<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dmitry Protsenko</title>
    <description>The latest articles on DEV Community by Dmitry Protsenko (@protsenko).</description>
    <link>https://dev.to/protsenko</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3026459%2F0811c74c-1143-4415-8a97-f725a3ecdfa5.png</url>
      <title>DEV Community: Dmitry Protsenko</title>
      <link>https://dev.to/protsenko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/protsenko"/>
    <language>en</language>
    <item>
      <title>Spring Data JPA Best Practices: Repositories Design Guide</title>
      <dc:creator>Dmitry Protsenko</dc:creator>
      <pubDate>Mon, 17 Nov 2025 14:23:26 +0000</pubDate>
      <link>https://dev.to/protsenko/spring-data-jpa-best-practices-repositories-design-guide-2f6j</link>
      <guid>https://dev.to/protsenko/spring-data-jpa-best-practices-repositories-design-guide-2f6j</guid>
      <description>&lt;p&gt;In this series of articles, I'm sharing my view on refactoring a large legacy codebase that employed many poor practices. To address these issues and develop better Spring Data JPA repositories, I wrote this guide to promote good practices in developing among my former colleagues. The guide is updated and completely rewritten to utilize the latest features of Spring Data JPA.&lt;/p&gt;

&lt;p&gt;Some of the examples may seem obvious, but they're not. It's only from your experienced perspective. They're real examples from the production codebase.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep in mind that this series of articles explains the latest version of Spring Data JPA, so there may be nuances that I'll highlight differently.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;1 Designing Spring Data JPA repositories&lt;/li&gt;
&lt;li&gt;2 Working with queries in repositories&lt;/li&gt;
&lt;li&gt;3 Spring Data JPA projections&lt;/li&gt;
&lt;li&gt;4 Using repository methods effectively&lt;/li&gt;
&lt;li&gt;5 Stored procedures in repositories&lt;/li&gt;
&lt;li&gt;
6 Spring Data Jpa Repositories Cheetsheet

&lt;ul&gt;
&lt;li&gt;6.1 What Spring Data JPA repositories to choose?&lt;/li&gt;
&lt;li&gt;6.2 How to query data with Spring Data JPA?&lt;/li&gt;
&lt;li&gt;6.3 Best ways to use Spring Data JPA Projections&lt;/li&gt;
&lt;li&gt;6.4 Short notes how to use queries effectively&lt;/li&gt;
&lt;li&gt;6.5 Calling stored procedures from Spring Data JPA&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;At the end&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  1 Designing Spring Data JPA repositories
&lt;/h3&gt;

&lt;p&gt;Spring Data JPA provides several repository interfaces with predefined methods for fetching data. I'll mention only the interesting&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;`Repository&amp;lt;T, ID&amp;gt;`&lt;/code&gt; interface, the father of Spring Data interfaces, is a marker interface for discovery. It has no methods. When using it, you define only what you want.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;CrudRepository&lt;/code&gt; interface, adds basic CRUD methods for faster development, and its twin &lt;code&gt;ListCrudRepository&lt;/code&gt; does the same, but returns &lt;code&gt;List&lt;/code&gt; instead of &lt;code&gt;Iterable&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;PagingAndSortingRepository&lt;/code&gt; - adds pagination and sorting only, and it has a twin that returns &lt;code&gt;List&lt;/code&gt;. Guess how it's called, wait a minute, you're right!&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;JpaRepository&lt;/code&gt; is my favorite, it contains all of the previous interfaces that return &lt;code&gt;List&lt;/code&gt;. Most of the time, I'm using only this interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When should you use a &lt;code&gt;Repository&lt;/code&gt; and &lt;code&gt;JpaRepository&lt;/code&gt;, or something in between? I believe that if you need a strict API for other developers, you can extend from the repository and implement only the necessary operations, rather than granting access to the entire CRUD operations, which could compromise your logic. Use &lt;code&gt;JpaRepository&lt;/code&gt; in case you don't have access limitations, and you want faster development.&lt;/p&gt;

&lt;p&gt;As an example of API limitations, you may sometimes need to work with logic stored in the database. There are numerous stored procedures, nuances in logic, and more. As a developer, you should be cautious when working with table entities, as this could lead to unpredictable behavior. So, in this case, you're only designing JPA entities and implementing only an empty interface with only specified query methods. With this approach, you're highlighting to other developers that they should implement methods that you need, rather than manipulating raw entities.&lt;/p&gt;

&lt;p&gt;Here is actually one more interesting thing that comes from Spring Data JPA repositories. Methods you inherit from &lt;code&gt;CrudRepository&lt;/code&gt;/&lt;code&gt;JpaRepository&lt;/code&gt; are transactional by default: reads run with &lt;code&gt;@Transactional(readOnly = true)&lt;/code&gt;, writes with a regular &lt;code&gt;@Transactional&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You usually don’t need Spring Framework &lt;code&gt;@Repository&lt;/code&gt; annotation (don't mistake it with JPA's interface) on the interface - discovery is automatic. For reusable bases, annotate the base interface with &lt;code&gt;@NoRepositoryBean&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Extending one of these interfaces informs Spring Data JPA that it should generate an implementation for your interface. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public interface CompanyRepository extends JpaRepository&amp;lt;Company, Long&amp;gt; {
    // custom methods will be added here
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2 Working with queries in repositories
&lt;/h3&gt;

&lt;p&gt;There are two primary methods for querying data using Spring Data JPA repositories. Actually, it's more, but let's focus on the more popular (IMO).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Derive the query from the method name&lt;/strong&gt;. Spring parses the method name and generates the appropriate JPQL. This speeds up development and is intuitive for simple conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the query explicitly with the &lt;code&gt;@Query&lt;/code&gt; annotation&lt;/strong&gt;. This approach is more flexible, allowing you to use JPQL or native SQL. In the latest versions of Spring Data, you can use &lt;code&gt;@NativeQuery&lt;/code&gt; an annotation instead of passing &lt;code&gt;nativeQuery = true&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For data-modifying queries (UPDATE/DELETE), add &lt;code&gt;@Modifying&lt;/code&gt;, and make sure there is a transactional boundary - either annotate the repository method or class with &lt;code&gt;@Transactional&lt;/code&gt;. Another way is to call it from a &lt;code&gt;@Transactional&lt;/code&gt; service.&lt;/p&gt;

&lt;p&gt;Example methods using both approaches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Derived query
List&amp;lt;Employee&amp;gt; findByDepartmentIdAndActiveTrue(Long departmentId);

// Explicit JPQL query
@Query("SELECT e FROM Employee e WHERE e.department.id = :deptId AND e.active = true")
List&amp;lt;Employee&amp;gt; findActiveEmployees(@Param("deptId") Long departmentId);

// Native SQL query
@Modifying
@Transactional
@NativeQuery(value = "UPDATE employee SET active = false WHERE id = :id")
void deactivateEmployee(@Param("id") Long id);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above, the first two methods are select queries. The last one is an update (deactivation), serving a different purpose than the selects.&lt;/p&gt;

&lt;p&gt;The first approach shortens the time required to develop queries and is intuitive. The second example provides additional capabilities when creating methods for working with the database, allowing you to write queries using both JPQL and native SQL.&lt;/p&gt;

&lt;p&gt;Inherited data-modifying methods are marked &lt;code&gt;@Transactional&lt;/code&gt; by default, as mentioned before. For custom modifying queries, annotate with &lt;code&gt;@Modifying&lt;/code&gt; and ensure a transactional boundary is present (on the method or class, or at the service layer).&lt;/p&gt;

&lt;h3&gt;
  
  
  3 Spring Data JPA projections
&lt;/h3&gt;

&lt;p&gt;Using raw entities for users from the database may be impractical or unsafe. It may be acceptable to retrieve the full entity and work within the application, but it's better to adapt your queries to return only the necessary information.&lt;/p&gt;

&lt;p&gt;To address this, you should utilize Spring Data JPA projections, which enable the definition of how data from the database will be presented. In the examples described above, the Spring Data JPA projection returns only the selected attributes needed by the caller.&lt;/p&gt;

&lt;p&gt;Spring Data JPA provides the following types of projections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Projections defined via interfaces are also known as interface-based projections&lt;/li&gt;
&lt;li&gt;Projections to DTO objects. Read the guide about &lt;a href="https://protsenko.dev/spring-data-jpa-best-practices-entity-design-guide/#h-7-developing-dtos" rel="noopener noreferrer"&gt;developing DTOs&lt;/a&gt; in a series of articles about Spring Data JPA.&lt;/li&gt;
&lt;li&gt;Dynamic projections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Interface-based projection&lt;/strong&gt; allows you to create read-only projections for safely presenting data from the database. This approach is typically used when there is no need to manipulate the created object, and it is required only for displaying data. Note that accessing nested properties can result in joins and additional queries, so projections are not always faster than fetching entities. Always check the generated SQL to ensure optimal performance.&lt;/p&gt;

&lt;p&gt;For example, an interface-based Spring Data JPA projection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public interface EmployeeView {
    String getFirstName();
    String getLastName();
    BigDecimal getSalary();
}

List&amp;lt;EmployeeView&amp;gt; findBySalaryGreaterThan(BigDecimal amount);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;DTO-based projection&lt;/strong&gt; enables projecting onto Java classes, allowing you to work with concrete DTO objects rather than interfaces. For derived query methods, Spring can map results to a DTO via its constructor, while for &lt;code&gt;@Query&lt;/code&gt; JPQL requires the use of a constructor expression. Class-based projections require a single all-args constructor; if there are multiple constructors, annotate the intended one with &lt;code&gt;@PersistenceCreator.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class EmployeeDto {
    private final String firstName;
    private final String lastName;
    private final BigDecimal salary;
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public BigDecimal getSalary() { return salary; }

    public EmployeeDto(String firstName, String lastName, BigDecimal salary) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.salary = salary;
    }
}

@Query("SELECT new com.example.EmployeeDto(e.firstName, e.lastName, e.salary) FROM Employee e WHERE e.salary &amp;gt; :amount")
List&amp;lt;EmployeeDto&amp;gt; findHighEarningEmployees(@Param("amount") BigDecimal amount);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;You can use dynamic projections&lt;/strong&gt; with repositories to expose a generic method, allowing the caller to choose the projection type at runtime. The &lt;code&gt;Class&lt;/code&gt; parameter selects the projection type. If you need to pass a &lt;code&gt;Class&lt;/code&gt; into the query itself, use a different parameter so it is not consumed as the projection selector.&lt;/p&gt;

&lt;p&gt;When using DTO classes with dynamic projections, ensure the query supplies the constructor arguments (for example, via a JPQL constructor expression); otherwise, the call will fail at runtime.&lt;/p&gt;

&lt;p&gt; List findBySalaryGreaterThan(BigDecimal amount, Class type);  &lt;/p&gt;

&lt;p&gt;// Usage:&lt;br&gt;&lt;br&gt;
repo.findBySalaryGreaterThan(new BigDecimal("1000"), EmployeeView.class);   // interface projection&lt;br&gt;&lt;br&gt;
repo.findBySalaryGreaterThan(new BigDecimal("1000"), EmployeeDto.class);    // DTO class (with suitable query)&lt;/p&gt;
&lt;h3&gt;
  
  
  4 Using repository methods effectively
&lt;/h3&gt;

&lt;p&gt;Repository CRUD methods run in a transaction by default (&lt;code&gt;readOnly = true&lt;/code&gt; for reads, regular for writes&lt;code&gt;)&lt;/code&gt; as it was mentioned before. The next thing about transaction is to avoiding opening transactions manually at call sites.&lt;/p&gt;

&lt;p&gt;When performing operations on multiple entities, prefer batch methods such as &lt;code&gt;saveAll()&lt;/code&gt; instead of calling &lt;code&gt;save()&lt;/code&gt; in a loop. Grouping actions into a single query reduces the number of database round-trips.&lt;/p&gt;

&lt;p&gt;Prefer batch-oriented writes, but note that &lt;code&gt;saveAll()&lt;/code&gt; it does not issue a single SQL statement by itself. To actually reduce round-trips, enable JDBC batching (for example, &lt;code&gt;spring.jpa.properties.hibernate.jdbc.batch_size=50&lt;/code&gt; and often &lt;code&gt;hibernate.order_inserts=true&lt;/code&gt;/&lt;code&gt;hibernate.order_updates=true&lt;/code&gt;). Avoid &lt;code&gt;GenerationType.IDENTITY&lt;/code&gt; if you need insert batching, and for very large batches, call &lt;code&gt;flush()&lt;/code&gt;/&lt;code&gt;clear()&lt;/code&gt; periodically.&lt;/p&gt;

&lt;p&gt;Whenever possible, combine logic into a single query rather than performing multiple queries in Java. In some cases, it is more efficient to offload part of an algorithm to the database using SQL.&lt;/p&gt;

&lt;p&gt;For large result sets, use pagination. &lt;code&gt;Page&amp;lt;T&amp;gt;&lt;/code&gt; returns content plus totals and triggers a count query (for custom &lt;code&gt;@Query&lt;/code&gt; supply a &lt;code&gt;countQuery&lt;/code&gt;), &lt;code&gt;Slice&amp;lt;T&amp;gt;&lt;/code&gt; returns content and whether there is a next slice without a count query, and a &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; with a &lt;code&gt;Pageable&lt;/code&gt; parameter applies limit/offset but gives no metadata.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// 1) Derived query with Page and sorting
interface UserRepository extends JpaRepository&amp;lt;User, Long&amp;gt; {
    Page&amp;lt;User&amp;gt; findByActive(boolean active, Pageable pageable);
}

// Usage:
Pageable pageable = PageRequest.of(0, 20, Sort.by("createdAt").descending());
Page&amp;lt;User&amp;gt; page = userRepository.findByActive(true, pageable);
List&amp;lt;User&amp;gt; users = page.getContent();
long total = page.getTotalElements();
boolean last = page.isLast();

// 2) Derived query with Slice for infinite scroll (no count query)
interface UserRepository extends JpaRepository&amp;lt;User, Long&amp;gt; {
    Slice&amp;lt;User&amp;gt; findByActive(boolean active, Pageable pageable);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5 Stored procedures in repositories
&lt;/h3&gt;

&lt;p&gt;While developing a database-oriented app, you could use Spring Data JPA to call stored procedures defined in your database. There are different ways to do it.&lt;/p&gt;

&lt;p&gt;The first method is to use &lt;code&gt;NamedStoredProcedureQuery&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Declare it on an entity with &lt;code&gt;@NamedStoredProcedureQuery&lt;/code&gt;, specifying:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; – the identifier used by JPA,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;procedureName&lt;/code&gt; – the actual name of the procedure in the database,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;parameters&lt;/code&gt; – an array of &lt;code&gt;@StoredProcedureParameter&lt;/code&gt; objects defining each parameter’s mode (&lt;code&gt;IN&lt;/code&gt;/&lt;code&gt;OUT&lt;/code&gt;), name and Java type.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Add a method to your repository annotated with &lt;code&gt;@Procedure&lt;/code&gt; and referencing the declared name.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For multiple outs parameters, Spring Data JPA can return a &lt;code&gt;Map&amp;lt;String,Object&amp;gt;&lt;/code&gt; when the call is backed by a &lt;code&gt;@NamedStoredProcedureQuery&lt;/code&gt;. For a single out, you can return that value directly. There’s also &lt;code&gt;outputParameterName&lt;/code&gt; on &lt;code&gt;@Procedure&lt;/code&gt; for targeting a specific out param.&lt;/p&gt;

&lt;p&gt;Example declaration on an entity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@NamedStoredProcedureQuery(
    name = "Employee.raiseSalary",
    procedureName = "raise_employee_salary",
    parameters = {
        @StoredProcedureParameter(mode = ParameterMode.IN,  name = "in_employee_id", type = Long.class),
        @StoredProcedureParameter(mode = ParameterMode.IN,  name = "in_increase",    type = BigDecimal.class),
        @StoredProcedureParameter(mode = ParameterMode.OUT, name = "out_new_salary", type = BigDecimal.class)
    }
)
@Entity
public class Employee { … }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repository method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Procedure(name = "Employee.raiseSalary")
BigDecimal raiseSalary(@Param("in_employee_id") Long id,
                       @Param("in_increase")    BigDecimal increase);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second method is to call a procedure without defining JPA metadata by using &lt;code&gt;@Procedure(procedureName = "…")&lt;/code&gt; directly on the repository method, or even call it via &lt;code&gt;@Query(value = "CALL proc(:arg…)", nativeQuery = true)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Actually, there is one more method, but it's less canonical is to use entity manager to call stored procedures, this article will not cover this practice as it will be in the next article and last article in this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  6 Spring Data Jpa Repositories Cheetsheet
&lt;/h2&gt;

&lt;p&gt;To briefly sum up this design guide, you could use the following cheetsheet.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 What Spring Data JPA repositories to choose?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Interfaces to extend&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Repository&amp;lt;T, ID&amp;gt;&lt;/code&gt; — marker only; you define every method yourself.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CrudRepository&amp;lt;T, ID&amp;gt;&lt;/code&gt; — basic CRUD; collections return &lt;code&gt;Iterable&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ListCrudRepository&amp;lt;T, ID&amp;gt;&lt;/code&gt; — like &lt;code&gt;CrudRepository&lt;/code&gt;, but collections return &lt;code&gt;List&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PagingAndSortingRepository&amp;lt;T, ID&amp;gt;&lt;/code&gt; — adds paging &amp;amp; sorting.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ListPagingAndSortingRepository&amp;lt;T, ID&amp;gt;&lt;/code&gt; — list-returning twin.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JpaRepository&amp;lt;T, ID&amp;gt;&lt;/code&gt; — all of the above + JPA niceties (&lt;code&gt;flush&lt;/code&gt;, batch deletes, etc.). Default choice in most apps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to pick what&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Need a &lt;strong&gt;strict, minimal API&lt;/strong&gt;? Extend &lt;code&gt;Repository&lt;/code&gt; (or a slim base) and expose only allowed methods.&lt;/li&gt;
&lt;li&gt;Need &lt;strong&gt;speed of development&lt;/strong&gt;? Extend &lt;code&gt;JpaRepository&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Discovery &amp;amp; base config&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@Repository&lt;/code&gt; &lt;strong&gt;is not required&lt;/strong&gt; on repository interfaces; Spring detects them by type.&lt;/li&gt;
&lt;li&gt;For reusable base interfaces, annotate with &lt;code&gt;@NoRepositoryBean&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Default implementation is backed by &lt;code&gt;SimpleJpaRepository&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Transactions (defaults)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Defaults apply to &lt;strong&gt;inherited CRUD methods&lt;/strong&gt;: reads use &lt;code&gt;@Transactional(readOnly = true)&lt;/code&gt;, writes use regular &lt;code&gt;@Transactional&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your own query methods&lt;/strong&gt; (derived names or &lt;code&gt;@Query&lt;/code&gt;) are &lt;strong&gt;not transactional by default&lt;/strong&gt;; annotate them or invoke from a transactional service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6.2 How to query data with Spring Data JPA?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Two core approaches&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Derived queries (by method name)&lt;/strong&gt; for simple conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit queries&lt;/strong&gt; with &lt;code&gt;@Query&lt;/code&gt; (JPQL) or &lt;strong&gt;native&lt;/strong&gt; queries via either &lt;code&gt;@Query(..., nativeQuery = true)&lt;/code&gt; &lt;strong&gt;or&lt;/strong&gt; &lt;code&gt;@NativeQuery&lt;/code&gt; (modern shortcut; supports extras like &lt;code&gt;sqlResultSetMapping&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Modifying queries&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;@Modifying&lt;/code&gt; and ensure a &lt;strong&gt;transactional boundary&lt;/strong&gt; (&lt;code&gt;@Transactional&lt;/code&gt; on method/class or call from a transactional service).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Paging with custom queries&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With &lt;code&gt;Page&amp;lt;T&amp;gt;&lt;/code&gt; and complex JPQL/native queries, supply an explicit &lt;code&gt;countQuery&lt;/code&gt; (or &lt;code&gt;countProjection&lt;/code&gt;) to avoid brittle auto-counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6.3 Best ways to use Spring Data JPA Projections
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Types&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interface-based projections&lt;/strong&gt; — read-only views for safe data presentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DTO/class-based projections&lt;/strong&gt; — map to a class with a single all-args constructor (use &lt;code&gt;@PersistenceCreator&lt;/code&gt; if multiple constructors exist).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic projections&lt;/strong&gt; — expose a generic method and let callers pass &lt;code&gt;Class&amp;lt;T&amp;gt;&lt;/code&gt; to choose the projection type at runtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Notes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accessing nested properties in projections can trigger joins. Projections aren’t automatically faster than entities. Inspect SQL and returned columns and measure queries performance.&lt;/li&gt;
&lt;li&gt;When using DTOs with dynamic projections, ensure the query provides constructor args (e.g., via a JPQL constructor expression).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6.4 Short notes how to use queries effectively
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Batching &amp;amp; round-trips&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefer &lt;code&gt;saveAll(...)&lt;/code&gt; over repeated &lt;code&gt;save(...)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Avoid &lt;code&gt;GenerationType.IDENTITY&lt;/code&gt; if you need insert batching. Prefer sequences/pooled optimizers.&lt;/li&gt;
&lt;li&gt;For very large batches, periodically call &lt;code&gt;flush()&lt;/code&gt;/&lt;code&gt;clear()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Let the DB work&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Push set-based logic into single queries instead of multi-step Java loops where possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Paging options&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Page&amp;lt;T&amp;gt;&lt;/code&gt; — content + totals (&lt;strong&gt;triggers count query&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Slice&amp;lt;T&amp;gt;&lt;/code&gt; — content + “has next” (&lt;strong&gt;no count query&lt;/strong&gt;, good for infinite scroll).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; with a &lt;code&gt;Pageable&lt;/code&gt; parameter — applies limit/offset, &lt;strong&gt;no metadata&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6.5 Calling stored procedures from Spring Data JPA
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Approaches&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Named stored procedure&lt;/strong&gt;: declare on an entity with &lt;code&gt;@NamedStoredProcedureQuery&lt;/code&gt;, then call via repository method annotated with &lt;code&gt;@Procedure(name = "...")&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct call (no entity metadata)&lt;/strong&gt;: use &lt;code&gt;@Procedure(procedureName = "...")&lt;/code&gt; on the repository method, or call using &lt;code&gt;@Query(value = "CALL ...", nativeQuery = true)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Outputs&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple &lt;code&gt;OUT&lt;/code&gt; params (with a named stored procedure) can be returned as &lt;code&gt;Map&amp;lt;String,Object&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Single &lt;code&gt;OUT&lt;/code&gt; can be returned directly, or target a specific one using &lt;code&gt;outputParameterName&lt;/code&gt; on &lt;code&gt;@Procedure&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  At the end
&lt;/h2&gt;

&lt;p&gt;I hope you find this article helpful. The continuation of the series articles will be published soon, so connect with me on &lt;a href="https://www.linkedin.com/in/protsenkodev/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; to stay informed about new articles. If you missed the previous article, read my “&lt;a href="https://protsenko.dev/spring-data-jpa-best-practices-entity-design-guide/" rel="noopener noreferrer"&gt;Spring Data JPA Best Practices: Entity Design Guide&lt;/a&gt;"&lt;/p&gt;

&lt;p&gt;Bye!&lt;/p&gt;

</description>
      <category>spring</category>
      <category>springboot</category>
      <category>hibernate</category>
      <category>java</category>
    </item>
    <item>
      <title>Spring Data JPA Best Practices: Entity Design Guide</title>
      <dc:creator>Dmitry Protsenko</dc:creator>
      <pubDate>Wed, 05 Nov 2025 16:36:46 +0000</pubDate>
      <link>https://dev.to/protsenko/spring-data-jpa-best-practices-entity-design-guide-ad</link>
      <guid>https://dev.to/protsenko/spring-data-jpa-best-practices-entity-design-guide-ad</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;You can enjoy the original version of this article on my website, &lt;a href="https://protsenko.dev/spring-data-jpa-best-practices-entity-design-guide/" rel="noopener noreferrer"&gt;protsenko.dev&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This series of articles was written as part of my review of a large legacy code base that employed many poor practices. To address these issues, I created this guide to promote Spring Data JPA Best Practices in designing entities among my former colleagues.&lt;/p&gt;

&lt;p&gt;It's a time to clean the dust from the guide, update it, and publish it for a broader audience. The guide is extensive, and I decided to break it down into three separate articles.&lt;/p&gt;

&lt;p&gt;Other articles in the series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://protsenko.dev/spring-data-jpa-best-practices-repositories-design-guide/" rel="noopener noreferrer"&gt;Spring Data JPA Best Practices: Repositories Design Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of the examples may seem obvious, but they're not. It's only from your experienced perspective. They're real examples from the production codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  1 Diving deep into Spring Data JPA
&lt;/h2&gt;

&lt;p&gt;For convenient and rapid development of database‑driven software, it is recommended to use the following libraries and frameworks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spring Boot&lt;/strong&gt; — simplifies building web applications on top of the Spring Framework by providing auto-configuration, starter dependencies, and opinionated defaults (e.g., embedded server, Actuator). It leverages Spring’s existing dependency-injection model rather than introducing a new one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spring Data JPA&lt;/strong&gt; saves time when creating repositories for database operations. It provides ready‑made interfaces for CRUD operations, transaction management, and query definition via annotations or method names. Another advantage is its integration with the Spring context, along with the corresponding benefits of dependency injection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lombok&lt;/strong&gt; – reduces boilerplate by generating getters, setters, and other repetitive code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Entities represent rows of a database table. They are plain Java objects annotated with &lt;code&gt;@Entity&lt;/code&gt; other JPA annotations. DTOs (Data Transfer Objects) are plain Java objects used for presenting data in a limited or transformed form compared to the underlying entity.&lt;/p&gt;

&lt;p&gt;In a Spring application, a &lt;strong&gt;repository&lt;/strong&gt; is a special interface that provides access to the database/data. Such repositories are typically annotated with &lt;code&gt;@Repository&lt;/code&gt;, but actually, you don't have to mark it separately when you extend from &lt;code&gt;JpaRepository&lt;/code&gt;, &lt;code&gt;CrudRepository&lt;/code&gt; or another Spring Data JPA repository. If you don’t extend a Spring Data base interface, you can use &lt;code&gt;@RepositoryDefinition&lt;/code&gt;. Also, use &lt;code&gt;@NoRepositoryBean&lt;/code&gt; on shared base interfaces&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;service&lt;/strong&gt; is a special class that encapsulates business logic and functionality. &lt;strong&gt;Controllers&lt;/strong&gt; are the endpoints of your application; users interact with controllers, which in turn inject services rather than repositories.&lt;/p&gt;

&lt;p&gt;For clarity, your project should be organized into packages by responsibility or others. Code organization is a good topic and always relies on your service, code agreements, etc. The given example represents a microservice with a single business domain.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;entity&lt;/code&gt; – database entities,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;repository&lt;/code&gt; – data access repositories,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;service&lt;/code&gt; – services, including wrappers for stored procedures,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;controller&lt;/code&gt; – application endpoints,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dtos&lt;/code&gt; – DTO classes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Connection to the database is auto-configured when a Spring Boot application starts, based on &lt;code&gt;application.properties&lt;/code&gt;/&lt;code&gt;application.yml&lt;/code&gt;. Common properties include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;spring.datasource.url&lt;/code&gt; – database connection URL&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spring.datasource.`driver-class-name`&lt;/code&gt; – database driver class, Spring Boot can often infer it from the JDBC URL, and set it only if inference fails.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spring.jpa.database-platform&lt;/code&gt; – SQL dialect to use&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spring.jpa.hibernate.ddl-auto&lt;/code&gt; – how Hibernate should create a database schema, available values: &lt;code&gt;none|validate|update|create|create-drop&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2 Developing entities with Spring Data JPA
&lt;/h2&gt;

&lt;p&gt;When designing software that interacts with a database, simple Java objects, properly used with Java Persistence API (JPA) annotations, play a crucial role. Such objects typically contain fields that map to table columns and are referred to as entities. Not every field maps one-to-one: relationships, embedded value objects, and &lt;code&gt;@Transient&lt;/code&gt; fields are common.&lt;/p&gt;

&lt;p&gt;At a minimum, an entity class must be annotated &lt;code&gt;@Entity&lt;/code&gt; to mark the class as a database entity and declare a primary key with &lt;code&gt;@Id&lt;/code&gt; or &lt;code&gt;@EmbeddedId&lt;/code&gt;. JPA also requires a no-arg constructor (public or protected). It is also good practice to include &lt;code&gt;@Table&lt;/code&gt; to explicitly define the target table. The &lt;code&gt;@Table&lt;/code&gt; annotation is an optional use it when you need to override the default one.&lt;/p&gt;

&lt;p&gt;When using &lt;code&gt;@Entity&lt;/code&gt; annotation, prefer to set the &lt;code&gt;name&lt;/code&gt; attribute, because this name is used in JPQL queries. If you omit it, JPQL uses the simple class name, setting it decouples queries from refactors*&lt;em&gt;.&lt;/em&gt;*&lt;/p&gt;

&lt;p&gt;There is one more useful annotation &lt;code&gt;@Table&lt;/code&gt; that helps you choose the name of the table if it differs from the naming strategy.&lt;/p&gt;

&lt;p&gt;The following examples demonstrate bad and good usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Entity
@Table(name = "COMPANY")
public class CompanyEntity {
    // fields omitted
}

// Later:
Query q = entityManager.createQuery("FROM " + CompanyEntity.class.getSimpleName() + " c")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the name attribute is missing on &lt;code&gt;@Entity&lt;/code&gt;, so the class name is used in queries. This can lead to fragile code when refactoring. Here is another problem: it's using the entityManager instead of a preconfigured Spring Data JPA repository. It provides more flexibility, but lets you make a mess in the codebase instead of using more preferable ways to fetch the data.&lt;/p&gt;

&lt;p&gt;Did you catch one more bad practice here? Definitely, it's a concatenation of strings to build a query. In that case, it wouldn't lead to SQL injection, but it's best to avoid this approach, especially if you pass the user input to query like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Entity(name = "Company")
@Table(name = "COMPANY")
public class CompanyEntity {
    // fields omitted
}

// Later:
Query q = entityManager.createQuery("FROM Company c");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the improved version, the entity name is explicitly specified, so JPQL queries can refer to the entity by name instead of relying on the class name.&lt;/p&gt;

&lt;p&gt;Note: the JPQL entity name and the physical table name in &lt;code&gt;@Table&lt;/code&gt; are independent concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  3 Avoiding magic numbers/literals
&lt;/h2&gt;

&lt;p&gt;Choose the type of your fields wisely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If a field represents a numeric enumeration, then use &lt;code&gt;Integer&lt;/code&gt; or an appropriately small numeric type.&lt;/li&gt;
&lt;li&gt;If selecting types, then base them on domain range and nullability (use wrapper types, such as &lt;code&gt;Integer&lt;/code&gt;, if the column can be &lt;code&gt;null&lt;/code&gt;); and remember that smaller numeric types rarely yield real benefits in JPA.&lt;/li&gt;
&lt;li&gt;If a value is monetary or requires precision, then use &lt;code&gt;BigDecimal&lt;/code&gt; with appropriate precision/scale.&lt;/li&gt;
&lt;li&gt;If you need details on enums, then they will be covered later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, suppose a field &lt;code&gt;statusCode&lt;/code&gt; represents the status of a company. Using a numeric type and documenting the meaning of each value in comments leads to code that is hard to read and error-prone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Company status:
// 1 – active
// 2 – suspended
// 3 – dissolved
// 4 – merged
@Column(name = "STATUS_CODE")
private Long statusCode;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, create an enumeration and use it as the type of the field. This makes the code self-documenting and reduces the chance of mistakes. When persisting an enum with Spring Data JPA, specify how it’s stored, it a good practice. Prefer &lt;code&gt;@Enumerated(EnumType.STRING)&lt;/code&gt; so the DB contains readable names, and you’re safe against reordering constants. Also, make sure the column type/length fits the enum names (set &lt;code&gt;length&lt;/code&gt; or &lt;code&gt;columnDefinition&lt;/code&gt; if needed).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Stored as readable names; ensure the column can hold them (e.g., length = 32).
@Column(name = "STATUS", length = 32)
@Enumerated(EnumType.STRING)
private CompanyStatus status;

public enum CompanyStatus {
    /** Active company */           ACTIVE,
    /** Temporarily suspended */    SUSPENDED,
    /** Officially dissolved */     DISSOLVED,
    /** Merged into another org */  MERGED;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your existing column stores numeric codes (e.g., 1–4) and must stay numeric, don’t use &lt;code&gt;EnumType.ORDINAL&lt;/code&gt; (it writes 0-based ordinals and will not match 1–4). Use an &lt;code&gt;AttributeConverter&amp;lt;CompanyStatus, Integer&amp;gt;&lt;/code&gt; to map explicit codes to enum values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Converter(autoApply = false)
public class CompanyStatusConverter implements AttributeConverter&amp;lt;CompanyStatus, Integer&amp;gt; {
    @Override
    public Integer convertToDatabaseColumn(CompanyStatus v) {
        if (v == null) return null;
        return switch (v) {
            case ACTIVE    -&amp;gt; 1;
            case SUSPENDED -&amp;gt; 2;
            case DISSOLVED -&amp;gt; 3;
            case MERGED    -&amp;gt; 4;
        };
    }

    @Override
    public CompanyStatus convertToEntityAttribute(Integer db) {
        if (db == null) return null;
        return switch (db) {
            case 1 -&amp;gt; CompanyStatus.ACTIVE;
            case 2 -&amp;gt; CompanyStatus.SUSPENDED;
            case 3 -&amp;gt; CompanyStatus.DISSOLVED;
            case 4 -&amp;gt; CompanyStatus.MERGED;
            default -&amp;gt; throw new IllegalArgumentException("Unknown STATUS_CODE: " + db);
        };
    }
}

// Keeps numeric 1..4 in the column while exposing a typesafe enum in Java.
@Column(name = "STATUS_CODE")
@Convert(converter = CompanyStatusConverter.class)
private CompanyStatus status;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4 Consistent use of types
&lt;/h2&gt;

&lt;p&gt;If a field is used in multiple entities, ensure it has the same type everywhere. Using different types for a conceptually identical field leads to ambiguous business logic. For example, the following bad usage shows two fields that represent a boolean flag but use different types and names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Bad choice of types for logically identical fields
// A – automatic, M – manual
@Column(name = "WAY_FLG")
private String wayFlg;

@Column(name = "WAY_FLG")
private Boolean wayFlg;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A better option is to use a &lt;code&gt;Boolean&lt;/code&gt; or, if you need more than two values or the two values are domain-labeled (e.g., Automatic/Manual), use an enum for both fields. If it’s truly binary yes/no, &lt;code&gt;Boolean&lt;/code&gt; (wrapper for nullable columns) is fine. Otherwise, prefer an enum for clarity and future-proofing. Below are consistent mappings without converters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Two labeled states: prefer an enum for clarity
public enum WayMode { A, M } // or AUTOMATIC, MANUAL

// Use the same mapping in every entity touching WAY_FLG
@Column(name = "WAY_FLG", length = 1) // ensure length fits enum names
@Enumerated(EnumType.STRING)
private WayMode wayFlg;

// Truly binary case (e.g., active/inactive):
@Column(name = "IS_ACTIVE")
private Boolean active; // use wrapper if the column can be NULL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is an intentional omission of the part about relations between tables in Spring Data JPA, as it is a broad subject that warrants a separate article on best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  5 Lombok usage
&lt;/h2&gt;

&lt;p&gt;To reduce the amount of boilerplate source code, it is recommended to use Lombok for code generation — but it should be used wisely. Generating getters and setters is an optimal choice. It’s best to stick to this practice and override getters and setters only if some pre-processing is required.&lt;/p&gt;

&lt;p&gt;For JPA, ensure a no-arg constructor exists. With Lombok, you can add &lt;code&gt;@NoArgsConstructor(access = AccessLevel.PROTECTED)&lt;/code&gt; to satisfy the spec cleanly.&lt;/p&gt;

&lt;p&gt;Warning note: Avoid &lt;code&gt;@Data&lt;/code&gt; on entities because its generated &lt;code&gt;equals&lt;/code&gt;/&lt;code&gt;hashCode&lt;/code&gt;/&lt;code&gt;toString&lt;/code&gt; can be problematic with JPA (lazy relations, mutable identifiers). Prefer targeted annotations (&lt;code&gt;@Getter&lt;/code&gt;, &lt;code&gt;@Setter&lt;/code&gt;, &lt;code&gt;@NoArgsConstructor&lt;/code&gt;) and, if needed, explicit equality with &lt;code&gt;@EqualsAndHashCode(onlyExplicitlyIncluded = true)&lt;/code&gt; and excludes for associations. Read more about this further.&lt;/p&gt;

&lt;p&gt;Among other things, Lombok supports the following commonly used annotations. You can find the full list on the website: &lt;a href="https://projectlombok.org/" rel="noopener noreferrer"&gt;https://projectlombok.org/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6 Overriding equals and hashCode
&lt;/h2&gt;

&lt;p&gt;Overriding &lt;code&gt;equals&lt;/code&gt; and &lt;code&gt;hashCode&lt;/code&gt; in database entities, many questions arise. For example, many applications work fine with the standard methods inherited from &lt;code&gt;Object&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context:&lt;/strong&gt; Within a single persistence context, Spring Data JPA/Hibernate already ensures identity semantics (same DB row -&amp;gt; same Java instance). You typically need custom &lt;code&gt;equals&lt;/code&gt;/&lt;code&gt;hashCode&lt;/code&gt; only if you rely on value semantics across contexts or use hashed collections.&lt;/p&gt;

&lt;p&gt;A database entity typically represents a real-world object, and you might choose to override in different ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Based on the entity’s primary key (it is immutable).&lt;/strong&gt;
&lt;strong&gt;Nuance:&lt;/strong&gt; if the ID is DB-generated, it’s &lt;code&gt;null&lt;/code&gt; before persist/flush. Handle transient state so you don’t change the hash while in a hashed collection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Based on a business key&lt;/strong&gt; (e.g., an employee’s tax ID/INN), since it isn’t tied to the database implementation.
&lt;strong&gt;Nuance:&lt;/strong&gt; works well if the key is unique, immutable, and always available; avoid mutable fields/associations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Based on all fields.&lt;/strong&gt;
&lt;strong&gt;Unsafe:&lt;/strong&gt; mutable data, potential lazy loads, recursion through associations, and performance costs make this fragile for JPA entities.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When should you override &lt;code&gt;equals&lt;/code&gt; and &lt;code&gt;hashCode&lt;/code&gt;?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When the object is used as a key in a &lt;code&gt;Map&lt;/code&gt;.&lt;/strong&gt;
&lt;strong&gt;Nuance:&lt;/strong&gt; don’t mutate fields used by &lt;code&gt;hashCode&lt;/code&gt; while the object is inside a hashed structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When using structures that store only unique objects (e.g., &lt;code&gt;Set&lt;/code&gt;).&lt;/strong&gt;
&lt;strong&gt;Nuance:&lt;/strong&gt; same caution—mutating equality/significant fields breaks collection invariants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When you need to compare database entities.&lt;/strong&gt;
&lt;strong&gt;Nuance:&lt;/strong&gt; often comparing &lt;strong&gt;identifiers&lt;/strong&gt; is sufficient; overriding is not mandatory if identity comparison fits your use case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the above, it follows that you should use Lombok’s &lt;code&gt;@EqualsAndHashCode&lt;/code&gt; and &lt;code&gt;@Data&lt;/code&gt; with caution, because Lombok generates these methods for &lt;strong&gt;all fields&lt;/strong&gt; unless configured otherwise.&lt;/p&gt;

&lt;p&gt;Expand: prefer &lt;code&gt;@EqualsAndHashCode(onlyExplicitlyIncluded = true)&lt;/code&gt; and mark only stable identifiers/business keys; avoid &lt;code&gt;@Data&lt;/code&gt; on entities (it's generated &lt;code&gt;equals&lt;/code&gt;/&lt;code&gt;hashCode&lt;/code&gt;/&lt;code&gt;toString&lt;/code&gt; can interact badly with lazy relations). You can also exclude relations from equality or &lt;code&gt;toString&lt;/code&gt; with &lt;code&gt;@EqualsAndHashCode.Exclude&lt;/code&gt; / &lt;code&gt;@ToString.Exclude&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Inheritance nuance: if you define equality in a mapped superclass, ensure the rule is consistent for all subclasses and matches how identity is defined for the whole hierarchy.&lt;/p&gt;

&lt;p&gt;A) Business-key equality (safe when the key is unique &amp;amp; immutable)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class Employee {
    private String taxId; // natural key: unique &amp;amp; immutable

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false; // keep simple here
        Employee other = (Employee) o;
        return taxId != null &amp;amp;&amp;amp; taxId.equals(other.taxId);
    }

    @Override
    public int hashCode() {
        return (taxId == null) ? 0 : taxId.hashCode();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;B) ID-based equality (handles transient state; avoids hash changes)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class Order {
    private Long id; // DB-generated

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Order other = (Order) o;
        // transient entities (id == null) are never equal to anything but themselves
        return id != null &amp;amp;&amp;amp; id.equals(other.id);
    }

    @Override
    public int hashCode() {
        // constant, avoids rehash after the ID is assigned later
        return getClass().hashCode();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;C) Lombok pattern (explicit includes; avoid all-fields default)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Getter
@Setter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Customer {
    @EqualsAndHashCode.Include
    private String externalId; // stable business key

    // exclude associations and mutable details
    // @EqualsAndHashCode.Exclude private List&amp;lt;Order&amp;gt; orders;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7 Developing DTOs
&lt;/h2&gt;

&lt;p&gt;DTOs (Data Transfer Objects) are specialized objects designed to present data to a client, as sending raw database entities directly to the client is considered a bad practice. Some teams do pass entities across internal boundaries, but for public/client-facing APIs, DTOs are preferred to avoid leakage of persistence details.&lt;/p&gt;

&lt;p&gt;Creating various DTOs increases development and maintenance time. If libraries like ModelMapper are used, there is also a memory overhead for object mapping.&lt;/p&gt;

&lt;p&gt;Another feature of DTOs is reducing the amount of data transmitted over the network and lowering the load on the DBMS by requesting fewer fields. The most important thing is that you actually reduce the database load only if you select fewer columns (using constructor expressions, Spring Data JPA projections, or native queries that return only the required fields). Fetching full entities and then mapping will not reduce the number of selected columns, your captain.&lt;/p&gt;

&lt;p&gt;There are different ways to design DTOs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using classes (objects). Classes or Java records are typically clearer for external APIs (serialization, validation, documentation).&lt;/li&gt;
&lt;li&gt;Using interfaces. Interfaces fit Spring Data interface-based projections (read-only, getter-only views), not write models.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are different ways to convert entity objects to DTOs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The optimal approach is to project data from the database directly into the required DTO. This both avoids extra mapping work and ensures fewer columns are selected.&lt;/li&gt;
&lt;li&gt;You can also use a library like ModelMapper. Prefer MapStruct instead (compile-time code generation, faster at runtime, explicit mappings).&lt;/li&gt;
&lt;li&gt;You can also write your own object converter. Handwritten mappers provide full control but increase maintenance requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good practices for developing DTOs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefer purpose-specific DTOs per use case (e.g., Summary/Detail/ListItem; CreateRequest vs Response).&lt;/li&gt;
&lt;li&gt;Avoid one mega-DTO tied to an entity, which causes over-fetching and tight coupling.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8 Spring Data JPA Summary Best Practices
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Developing entities with JPA annotations&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Entities map fields to columns; relationships, embeddables, and &lt;code&gt;@Transient&lt;/code&gt; are common (not always 1:1).&lt;/li&gt;
&lt;li&gt;Minimum: &lt;code&gt;@Entity&lt;/code&gt; + primary key (&lt;code&gt;@Id&lt;/code&gt; / &lt;code&gt;@EmbeddedId&lt;/code&gt;) + no-args ctor (public/protected).&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;@Table&lt;/code&gt; only to override defaults (table, schema, constraints).&lt;/li&gt;
&lt;li&gt;Prefer explicit &lt;code&gt;@Entity(name="…")&lt;/code&gt; to decouple JPQL from class names so JPQL stays stable across class renames.&lt;/li&gt;
&lt;li&gt;Avoid string concatenation in JPQL and use parameters.&lt;/li&gt;
&lt;li&gt;JPQL entity name (&lt;code&gt;@Entity(name)&lt;/code&gt;) and physical table name (&lt;code&gt;@Table(name)&lt;/code&gt;) are independent.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Avoiding magic numbers/literals&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Choose types by domain range and nullability; use wrapper types (&lt;code&gt;Integer&lt;/code&gt;, &lt;code&gt;Boolean&lt;/code&gt;) if the column can be &lt;code&gt;NULL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Money/precision -&amp;gt; &lt;code&gt;BigDecimal&lt;/code&gt; with proper precision/scale.&lt;/li&gt;
&lt;li&gt;Replace numeric codes with enums. Persist with &lt;code&gt;@Enumerated(EnumType.STRING)&lt;/code&gt; and ensure column length fits names.&lt;/li&gt;
&lt;li&gt;Legacy numeric code columns: use an &lt;code&gt;AttributeConverter&amp;lt;Enum, Integer&amp;gt;&lt;/code&gt;. Don’t use &lt;code&gt;EnumType.ORDINAL&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Consistent use of types&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Use the same Java type for the same conceptual column everywhere.&lt;/li&gt;
&lt;li&gt;Binary flags -&amp;gt; &lt;code&gt;Boolean&lt;/code&gt; (wrapper). Domain-labeled or future-expandable flags -&amp;gt; enum consistently.&lt;/li&gt;
&lt;li&gt;Map enums uniformly (&lt;code&gt;@Enumerated(EnumType.STRING)&lt;/code&gt;, &lt;code&gt;@Column(length=…)&lt;/code&gt;); avoid mixing &lt;code&gt;String&lt;/code&gt;/&lt;code&gt;Boolean&lt;/code&gt;/enum for the same column.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Lombok usage&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Use Lombok for boilerplate: &lt;code&gt;@Getter&lt;/code&gt;, &lt;code&gt;@Setter&lt;/code&gt;, &lt;code&gt;@NoArgsConstructor(access = PROTECTED)&lt;/code&gt; for JPA.&lt;/li&gt;
&lt;li&gt;Avoid &lt;code&gt;@Data&lt;/code&gt; on entities (generated &lt;code&gt;equals/hashCode/toString&lt;/code&gt; can conflict with lazy relations and identifiers).&lt;/li&gt;
&lt;li&gt;Override accessors only when pre-/post-processing is needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Overriding &lt;code&gt;equals&lt;/code&gt; and &lt;code&gt;hashCode&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Override only if you need value semantics across contexts or in hashed collections.&lt;/li&gt;
&lt;li&gt;Business-key strategy: compare a unique, immutable key.&lt;/li&gt;
&lt;li&gt;ID-based strategy: treat transient (&lt;code&gt;id == null&lt;/code&gt;) entities as unequal; use a stable/constant &lt;code&gt;hashCode()&lt;/code&gt; to avoid rehash after persist.&lt;/li&gt;
&lt;li&gt;Avoid all-fields equality; exclude associations to prevent lazy loads/recursion.&lt;/li&gt;
&lt;li&gt;With Lombok, prefer &lt;code&gt;@EqualsAndHashCode(onlyExplicitlyIncluded = true)&lt;/code&gt; and explicitly include stable identifiers; use &lt;code&gt;@EqualsAndHashCode.Exclude&lt;/code&gt; / &lt;code&gt;@ToString.Exclude&lt;/code&gt; for relations.&lt;/li&gt;
&lt;li&gt;Maintain consistency in equality rules across hierarchies (mapped superclasses vs subclasses).&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Developing DTOs&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Don’t expose entities to clients, even if you return them with annotation &lt;code&gt;@JsonIgnore&lt;/code&gt;; design purpose-specific DTOs (Summary/Detail/ListItem; Create/Update/Response).&lt;/li&gt;
&lt;li&gt;Reduce database load by selecting fewer columns: project directly to DTOs (using constructor expressions), utilize interface-based projections, or use native queries that return only the necessary fields.&lt;/li&gt;
&lt;li&gt;Mapping full entities doesn’t reduce selected columns.&lt;/li&gt;
&lt;li&gt;Prefer MapStruct (compile-time, fast, explicit) over ModelMapper; handwritten mappers give control at a higher maintenance cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  At the end
&lt;/h2&gt;

&lt;p&gt;I hope you find this article helpful. The continuation of the series articles will be published soon, so connect with me on &lt;a href="https://www.linkedin.com/in/protsenkodev/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; to stay informed about new articles. If you're curious about Spring Data JPA, read the next article: "&lt;a href="https://protsenko.dev/spring-data-jpa-best-practices-repositories-design-guide/" rel="noopener noreferrer"&gt;Spring Data JPA Best Practices: Repositories Design Guide&lt;/a&gt;"&lt;/p&gt;

&lt;p&gt;Bye!&lt;/p&gt;

</description>
      <category>spring</category>
      <category>springboot</category>
      <category>springdata</category>
      <category>programming</category>
    </item>
    <item>
      <title>Docker Best Practices to Secure and Optimize Your Containers</title>
      <dc:creator>Dmitry Protsenko</dc:creator>
      <pubDate>Mon, 29 Sep 2025 17:00:00 +0000</pubDate>
      <link>https://dev.to/protsenko/docker-best-practices-to-secure-and-optimize-your-containers-2e7a</link>
      <guid>https://dev.to/protsenko/docker-best-practices-to-secure-and-optimize-your-containers-2e7a</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;You can enjoy the original version of this article on my website, &lt;a href="https://protsenko.dev/docker-best-practices-to-secure-and-optimize-your-containers/" rel="noopener noreferrer"&gt;protsenko.dev&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hi! In this article, I’m sharing 29 collected Docker best practices to make your images better, more secure, and faster. These Docker Best Practices cover security, maintainability, and reproducibility. This guide is based on my experience creating the &lt;a href="https://plugins.jetbrains.com/plugin/25413-cloud-iac-security" rel="noopener noreferrer"&gt;Docker Scanner IntelliJ IDEA plugin&lt;/a&gt; and almost all of the practices covered by the scanner. It also includes Kubernetes Security Scanner features.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If this project or article has been helpful to you, please consider giving it a ⭐ on &lt;a href="https://github.com/NordCoderd/cloud-security-plugin" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; to help others discover it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Docker image size &amp;amp; maintainability
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1 Always pin an image version&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When you do not specify a version tag, Docker uses the latest version by default. This practice makes builds unpredictable. You do not know which image version you will download. If an attacker compromises the image author, they can push a harmful image. New image updates may also break your application. This is one of the most important Docker Best Practices and a &lt;a href="https://protsenko.dev/2025/04/02/top-7-dockerfile-security-tips-you-need-to-know/" rel="noopener noreferrer"&gt;Security Best Practice&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That was the first inspection that I implemented in the &lt;a href="https://plugins.jetbrains.com/plugin/25413-cloud-iac-security" rel="noopener noreferrer"&gt;Docker Scanner&lt;/a&gt; plugin. You can try it and see how flawlessly it works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
ARG version=latest
FROM ubuntu as u1
FROM ubuntu:latest as u2
FROM ubuntu:$version as u3
FROM u3
USER nobody
# fix
FROM ubuntu:noble as u1
FROM ubuntu@sha256:72297848456d5d37d1262630108ab308d3e9ec7ed1c3286a32fe09856619a782 as u2
FROM u2
USER nobody
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;2 Avoid using the dist-upgrade in package management&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Using &lt;code&gt;dist-upgrade&lt;/code&gt; can upgrade to a new major release. This behavior may break your Dockerfile by introducing unexpected changes. Dockerfiles should use controlled updates to maintain stability.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
RUN apt-get dist-upgrade
# fix is in using newer dist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;3 Use multi-stage builds to reduce image size&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Multi-stage builds allow you to use multiple FROM statements in your Dockerfile. You can copy artifacts from one stage to another, leaving behind everything you don’t want in the final image. Reducing the image size is a wide area where you could apply these Docker best practices. The next practices will be about this.&lt;/p&gt;

&lt;p&gt;This approach dramatically reduces the final image size by excluding build tools, source code, and intermediate files that are only needed during the build process. It also improves Docker security by not shipping development dependencies and build tools in production images.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/index.js"]

# fix
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM node:18-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm install --only=production &amp;amp;&amp;amp; npm cache clean --force

COPY --from=builder /app/dist ./dist
USER nobody
EXPOSE 3000
CMD ["node", "dist/index.js"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;4 Consolidate multiple RUN instructions&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Each &lt;code&gt;RUN&lt;/code&gt; instruction creates a new image layer, which increases the final image size and build time. Consolidating these commands into a single &lt;code&gt;RUN&lt;/code&gt; instruction improves efficiency and simplifies maintenance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
RUN apt-get -y --no-install-recommends install netcat
RUN apt-get clean
# fix
FROM ubuntu:20.04
RUN apt-get -y --no-install-recommends install netcat &amp;amp;&amp;amp; apt-get clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;5 Always clean the package manager's cache&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;There are plenty of package managers like: &lt;code&gt;apk&lt;/code&gt;, &lt;code&gt;dnf&lt;/code&gt;, &lt;code&gt;yum&lt;/code&gt;, &lt;code&gt;zypper&lt;/code&gt;, &lt;code&gt;pip&lt;/code&gt;. All of them cache the data, which helps to keep them performant, but it's absolutely redundant in the container. Cached data stays in Docker image layers, increasing its size.&lt;/p&gt;

&lt;p&gt;You could easily spot these issues with the &lt;a href="https://plugins.jetbrains.com/plugin/25413-cloud-iac-security" rel="noopener noreferrer"&gt;Docker Scanner&lt;/a&gt; plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# dnf
## problem
FROM fedora:version
RUN dnf install -y httpd
## fix
RUN dnf install -y httpd &amp;amp;&amp;amp; \
    dnf clean all &amp;amp;&amp;amp; \
    rm -rf /var/cache/dnf

# yum
## problem
FROM centos:7
RUN yum install -y httpd
## fix
FROM centos:7
RUN yum install -y httpd &amp;amp;&amp;amp; yum clean

# zypper
## problem
FROM opensuse/leap:15.3
RUN zypper install -y httpd
## fix
FROM opensuse/leap:15.3
USER nobody
RUN zypper install -y httpd &amp;amp;&amp;amp; zypper clean

# apk
## problem
FROM alpine:latest
RUN apk update &amp;amp;&amp;amp; \
    apk add curl
## fix
FROM alpine:latest
RUN apk add --no-cache curl

# pip
## problem
FROM python:3.9
USER nobody
RUN pip install django
RUN pip install -r requirements.txt
## fix
FROM python:3.9
USER nobody
RUN pip install --no-cache-dir django
RUN pip install --no-cache-dir -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;6 Always combine the package manager update command with the install&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Yes, you could notice it's almost the same as consolidating RUN commands, and you're right, but even without this problem taking place.&lt;/p&gt;

&lt;p&gt;Running the package manager update command alone updates the package list in a separate layer. This updated list may not be used if the installation occurs in another RUN statement. Combining the update and install commands in one RUN ensures that the package installation uses the latest package data.&lt;/p&gt;

&lt;p&gt;There is a package manager list for this practice : &lt;code&gt;apt-get&lt;/code&gt;, &lt;code&gt;apt&lt;/code&gt;, &lt;code&gt;yum&lt;/code&gt;, &lt;code&gt;apk&lt;/code&gt;, &lt;code&gt;dnf&lt;/code&gt;, and &lt;code&gt;zypper&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends build-essential
# fix
FROM ubuntu:20.04
RUN apt-get update &amp;amp;&amp;amp; apt-get install --no-install-recommends -y build-essential
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;7 Always use &lt;code&gt;--no-install-recommends&lt;/code&gt; with &lt;code&gt;apt-get&lt;/code&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When you run &lt;code&gt;apt-get&lt;/code&gt; install without &lt;code&gt;--no-install-recommends&lt;/code&gt;, &lt;code&gt;apt-get&lt;/code&gt; installs extra packages by default. This increases the image size and may add unwanted dependencies. Using &lt;code&gt;--no-install-recommends&lt;/code&gt; installs only the essential packages, reducing the image size and potential security risks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y build-essential
# fix
FROM ubuntu:20.04
RUN apt-get update &amp;amp;&amp;amp; apt-get install --no-install-recommends -y build-essential
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;8 Always use -l with useradd to avoid high-UID bloat&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When UID is large (hundreds or tens of thousands), useradd without -l writes to the file offset that is the same as UID in &lt;code&gt;/var/log/lastlog&lt;/code&gt; and &lt;code&gt;/var/log/faillog&lt;/code&gt;. As those databases use UID-based indexing, that large offset consumes a large sparse hole and immediately makes those files' logical (apparent) size larger. In layer container builds, those sparse blocks often get materialized as real bytes - farewell, tens of MB for nothing. Don't touch those databases by using &lt;code&gt;-l&lt;/code&gt; (&lt;code&gt;--no-log-init&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
RUN useradd -u 198401 nordcoderd
USER nordcoderd
# fix
FROM ubuntu:20.04
RUN useradd -u -l 198401 nordcoderd
USER nordcoderd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Best Practices to Keep Docker Files Clean
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;9 Always choose what to use: wget or curl&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Both &lt;code&gt;wget&lt;/code&gt; and &lt;code&gt;curl&lt;/code&gt; fetch remote files. Using both tools adds unnecessary redundancy and may lead to installing extra packages. Standardize on one tool to keep the Dockerfile clear and efficient. Make something clear, yet another of Docker's best practices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
RUN wget http://example.com/script.sh -O script.sh &amp;amp;&amp;amp; curl -sSL http://example.com/script.sh -o script2.sh
# fix
FROM ubuntu:20.04
RUN curl -sSL http://example.com/script.sh -o script.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;10 Use absolute paths&lt;/strong&gt; for &lt;strong&gt;WORKDIR&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For clarity and reliability, you should always use absolute paths for your &lt;code&gt;WORKDIR&lt;/code&gt;. Relative paths may cause unexpected behavior during builds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
WORKDIR app
# fix
FROM ubuntu:20.04
USER nobody
WORKDIR /app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  11 Exclude unnecessary files with .dockerignore
&lt;/h3&gt;

&lt;p&gt;When building Docker images, everything in the build context (the directory you run &lt;code&gt;docker build&lt;/code&gt; from) is sent to the Docker daemon. If you don’t exclude unnecessary files (like &lt;code&gt;.git&lt;/code&gt;, logs, node_modules, or temporary build artifacts), the build context becomes bloated, slowing down image builds and potentially leaking sensitive data into the image. The &lt;code&gt;.dockerignore&lt;/code&gt; file works like &lt;code&gt;.gitignore&lt;/code&gt;, preventing unwanted files from being included in the context and final image layers.&lt;/p&gt;

&lt;p&gt;Among the Docker Security Best Practices, one key guideline is to ensure that no sensitive data, such as &lt;code&gt;.env&lt;/code&gt; files, is copied into the image. Unfortunately, this rule isn't bundled in the &lt;a href="https://plugins.jetbrains.com/plugin/25413-cloud-iac-security" rel="noopener noreferrer"&gt;Docker Scanner&lt;/a&gt; plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM node:18
WORKDIR /app
COPY . .
RUN npm install &amp;amp;&amp;amp; npm run build
CMD ["node", "dist/index.js"]
## In this case, large local 'node_modules' or '.git' folder could be copied unnecessarily.
# fix

## Add a `.dockerignore` file in the project root:
.dockerignore
.git
*.log
node_modules
Dockerfile
.dockerignore

## Then use your Dockerfile cleanly:
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install --only=production
COPY . .
CMD ["node", "dist/index.js"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;12 Use WORKDIR instead of the cd command&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Using multiple &lt;code&gt;RUN cd ... &amp;amp;&amp;amp; ...&lt;/code&gt; instructions creates clutter. These commands are difficult to troubleshoot and maintain. Instead, use the &lt;code&gt;WORKDIR&lt;/code&gt; instruction to set the working directory consistently. This approach simplifies the Dockerfile and improves readability, which is fundamental for Docker best practices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
RUN cd /app &amp;amp;&amp;amp; make build &amp;amp;&amp;amp; test
# fix
FROM ubuntu:20.04
WORKDIR /app
RUN make build &amp;amp;&amp;amp; test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;13 Use JSON notation for CMD and ENTRYPOINT&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Docker supports two formats for &lt;code&gt;CMD&lt;/code&gt; and &lt;code&gt;ENTRYPOINT&lt;/code&gt;: shell form and JSON array form. Shell form can misinterpret arguments and cause errors. JSON notation parses each argument correctly and ensures consistent behavior.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
CMD echo "Hello World"
ENTRYPOINT /usr/local/bin/start-app
# fix
FROM ubuntu:20.04
CMD ["echo", "Hello World"]
ENTRYPOINT ["/usr/local/bin/start-app"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;14 Use apt-get or apt-cache instead of apt&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;apt&lt;/code&gt; is designed for interactive use and may prompt for input, which is not suitable for automated Docker builds. Using &lt;code&gt;apt&lt;/code&gt; may lead to errors or unexpected output. Instead, use &lt;code&gt;apt-get&lt;/code&gt; or &lt;code&gt;apt-cache&lt;/code&gt; for non-interactive package management.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
RUN apt update &amp;amp;&amp;amp; apt install -y build-essential
# fix
FROM ubuntu:20.04
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends build-essential
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;15 Use the package manager's auto-confirm flag -y&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Package managers like &lt;code&gt;apt-get&lt;/code&gt;, &lt;code&gt;yum&lt;/code&gt;, &lt;code&gt;dnf&lt;/code&gt;, and &lt;code&gt;zypper&lt;/code&gt; prompt for confirmation if the &lt;code&gt;-y&lt;/code&gt; flag is missing. Without this flag, installations may pause for manual input and block automated builds. You should care about the reproducibility of the image, which is very valuable in Docker best practices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
RUN apt-get update &amp;amp;&amp;amp; apt-get install --no-install-recommends build-essential\
# fix
FROM ubuntu:20.04
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends build-essential
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Docker Security Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;16 Avoid default, root, or dynamic user&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Running containers with an undefined user or with the &lt;code&gt;root&lt;/code&gt; user increases the attack surface. Dynamic assignment can override the intended user and make the container vulnerable. Note: this check only considers the final user specified in the Dockerfile. It is acceptable to run build operations as root if you later switch to a dedicated non-root user.&lt;/p&gt;

&lt;p&gt;These best practices apply to Docker security and &lt;a href="https://protsenko.dev/kubernetes-security-top-12-best-practices-to-protect-your-cluster/" rel="noopener noreferrer"&gt;Kubernetes Security&lt;/a&gt;. The &lt;a href="https://plugins.jetbrains.com/plugin/25413-cloud-iac-security" rel="noopener noreferrer"&gt;Docker Scanner&lt;/a&gt; plugin fully covers the following examples.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
## Example 1: Implicitly using the default user (can be overridden)
FROM ubuntu:20.04
RUN whoami
## Example 2: Explicitly setting the user to root
FROM ubuntu:20.04
USER root
RUN whoami
## Example 3: Dynamic user assignment via an environment variable (risky if overridden)
ARG APP_USER
FROM ubuntu:20.04
USER $APP_USER
RUN whoami
# fix
FROM ubuntu:20.04
## Create a dedicated non-root user and group
RUN groupadd --system app &amp;amp;&amp;amp; useradd --system --create-home --gid app app
## Switch to the non-root user
USER app
RUN whoami
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;17 Avoid exposing the SSH port&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Exposing it in your Dockerfile may allow unauthorized SSH access to the container, which does not usually need SSH access. Removing this exposure reduces the attack surface. Take care of this. This practice could also be applied to Kubernetes security. Exposing something sensitive is a bad idea everywhere.&lt;/p&gt;

&lt;p&gt;Note: The &lt;code&gt;EXPOSE&lt;/code&gt; doesn't automatically expose the port, it only highlights what should be exposed. However, the port could be accessible in the internal Docker network by other containers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
EXPOSE 22
# fix
FROM ubuntu:20.04
# EXPOSE 22 removed to prevent unauthorized SSH access.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;18 Avoid overriding ARG variables in RUN commands&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;ARG variables are set at build time. Users can override them with the &lt;code&gt;--build-arg&lt;/code&gt; flag. Critical commands may change if the &lt;code&gt;ARG&lt;/code&gt; is altered. This behavior can introduce security risks or unexpected outcomes, which is why it is a Docker security best practice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
ARG INSTALL_PACKAGE=build-essential
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y $INSTALL_PACKAGE
# fix
dockerfile
FROM ubuntu:20.04
USER nobody
RUN apt-get update &amp;amp;&amp;amp; apt-get install --no-install-recommends -y build-essential
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;19 Avoid pipe curl to bash (curl|bash)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Using &lt;code&gt;curl&lt;/code&gt; or &lt;code&gt;wget&lt;/code&gt; with a pipe (|) or redirection (&amp;gt;) to execute scripts directly poses a security risk. This practice, often referred to as curl | bash, should be approached with caution to avoid potential vulnerabilities. Executing something that was downloaded from a foreign website without validation is a worst practice that leads to Docker security issues/&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
RUN curl -sSL http://example.com/script.sh | sh
# fix
FROM ubuntu:20.04
RUN curl -sSL http://example.com/script.sh -o script.sh
# run only if downloaded script was verified
# or better to save file to build folder to not retrieve remote file each time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;20 Avoid storing secrets in ENV keys&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Storing secrets in &lt;code&gt;ENV&lt;/code&gt; Keys puts them directly into the image layers. Attackers can extract these secrets if they gain access to the image. Do not hardcode sensitive data in your Dockerfile. This best practice could also be extended by the previous rule: use &lt;code&gt;.dockerignore&lt;/code&gt; to not leave sensitive files in the Docker image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
ENV PASSWORD=supersecret123
# fix
FROM ubuntu:20.0
# Remove sensitive data from the Dockerfile.
# Inject PASSWORD at runtime using secure methods.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;21 Always prefer to use COPY instead of ADD&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ADD&lt;/code&gt; has extra features, such as extracting tar files and handling remote URLs. These features may cause unintended effects if you only need to copy files. Use &lt;code&gt;COPY&lt;/code&gt; to ensure simple and predictable behavior. The ADD function could lead to non-reproducible results if you’re depending on remote scripts/archives, and in the worst case, lead to security issues. It’s why you should avoid ADD to avoid additional security problems in your Dockerfile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
ADD ./app /app
# fix
FROM ubuntu:20.04
COPY ./app /app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  22 Always use ADD with checksum verification when downloading a file
&lt;/h3&gt;

&lt;p&gt;I know, sometimes you have to use &lt;code&gt;ADD&lt;/code&gt;. In that case, consider using &lt;code&gt;--checksum&lt;/code&gt; to verify the checksum of the remote source. It makes your Dockerfile secure and builds it more reproducibly. With checksum verification, you'll be sure the image has been built with a verified file, which reduces surface attack by changing the remote file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
ADD https://mirrors.edge.kernel.org/pub/linux/kernel/Historic/linux-0.01.tar.gz /
# fix
FROM ubuntu:20.04
ADD --checksum=sha256:24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d https://mirrors.edge.kernel.org/pub/linux/kernel/Historic/linux-0.01.tar.gz /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;23 Avoid using RUN with sudo&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Docker executes RUN commands as the user specified by the USER directive, often root by default. Including sudo in RUN The commands are redundant and may cause unexpected outcomes*&lt;em&gt;.&lt;/em&gt;* Avoid using &lt;code&gt;sudo&lt;/code&gt; to keep the builds clean and predictable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
RUN sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install -y --no-install-recommends build-essential
# fix
FROM ubuntu:20.04
RUN apt-get update &amp;amp;&amp;amp; sudo apt-get install -y --no-install-recommends build-essential
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  General Docker Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;24 Avoid the deprecated MAINTAINER instruction&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;MAINTAINER&lt;/code&gt; has been deprecated since Docker 1.13.0. Use LABEL to provide maintainer information. This change improves clarity and maintainability.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
MAINTAINER John Doe &amp;lt;john@protsenko.dev&amp;gt;
# fix
FROM ubuntu:20.04
LABEL org.opencontainers.image.authors="John Doe &amp;lt;john@protsenko.dev&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;25 Ensure trailing slash for COPY commands with multiple arguments&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When you copy multiple files, the destination must be a directory. If the destination does not end with a slash, Docker may misinterpret it. This mistake leads to build errors or unexpected file placements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
COPY file1.txt file2.txt dir
# fix
FROM ubuntu:20.04
COPY file1.txt file2.txt dir/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;26 Avoid duplicate aliases in FROM instructions&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Docker requires each &lt;code&gt;FROM&lt;/code&gt; instruction alias to be unique. Duplicate aliases lead to conflicts that stop the build process. They also reduce the clarity and maintainability of the Dockerfile. This problem you could find during the building time, but it's better to find it in the IDE via my open-source &lt;a href="https://plugins.jetbrains.com/plugin/25413-cloud-iac-security" rel="noopener noreferrer"&gt;Docker scanner&lt;/a&gt; plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20 as builder
RUN apt-get update &amp;amp;&amp;amp; apt-get install --no-install-recommends -y build-essential
FROM node:14 as builder
RUN npm install
# fix
FROM ubuntu:20 as builder-ubuntu
RUN apt-get update &amp;amp;&amp;amp; apt-get install --no-install-recommends -y build-essential
FROM node:14 as builder-node
RUN npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;27 Avoid self-referencing COPY –from instructions&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;COPY&lt;/code&gt; instruction with the &lt;code&gt;--from&lt;/code&gt; flag must refer to a previous build stage. Referencing the current stage alias is invalid because you cannot copy from the image you are currently building.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20 as builder
RUN apt-get update &amp;amp;&amp;amp; apt-get install --no-install-recommends -y build-essential

## Incorrect: referencing the current build stage "builder"
COPY --from=builder /app /app

# fix
FROM ubuntu:20 as builder
RUN apt-get update &amp;amp;&amp;amp; apt-get install --no-install-recommends -y build-essential

FROM ubuntu:20
## Correct: referencing the previous build stage "builder"
COPY --from=builder /app /app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;28 Avoid multiple CMD or ENTRYPOINT, or HEALTHCHECK instructions&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Defining multiple &lt;code&gt;CMD&lt;/code&gt; or &lt;code&gt;ENTRYPOINT&lt;/code&gt;, or &lt;code&gt;HEALTHCHECK&lt;/code&gt; instructions create confusion. Only the final instruction is used during container startup. This can lead to unintended commands running and make the Dockerfile harder to maintain. Maintainability is another aspect of Docker best practices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
## Multiple CMD instructions: only the last one is used.
CMD ["echo", "Hello"]
CMD ["echo", "World"]

## Multiple ENTRYPOINT instructions: only the last one is used.
ENTRYPOINT ["run-app"]
ENTRYPOINT ["start-app"]

# fix
FROM ubuntu:20.04
# Single CMD instruction ensures the correct command runs.
CMD ["echo", "Hello World"]
# Single ENTRYPOINT instruction ensures the correct entrypoint runs.
ENTRYPOINT ["start-app"]

## Multiple HEALTHCHECK instructions;
# problem
FROM ubuntu:20.04
HEALTHCHECK --interval=30s CMD curl -f http://localhost/ || exit 1
HEALTHCHECK --interval=30s CMD wget -q -O- http://localhost/ || exit 1
# fix
FROM ubuntu:20.04
HEALTHCHECK --interval=30s CMD curl -f http://localhost/ || exit 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;29 Avoid exposing ports outside the allowed range&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The Dockerfile declares an exposed port that falls outside the valid UNIX port range of 0-65535. Using an invalid port number is a misconfiguration that may cause errors during build or runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# problem
FROM ubuntu:20.04
EXPOSE 70000
# fix
FROM ubuntu:20.04
EXPOSE 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Words after Docker Best Practices
&lt;/h2&gt;

&lt;p&gt;It’s a good idea to integrate these checks into your development and CI/CD process. For the best IDE integration, give the &lt;a href="https://plugins.jetbrains.com/plugin/25413-cloud-iac-security" rel="noopener noreferrer"&gt;Docker Scanner plugin for JetBrains IDEs&lt;/a&gt; a try. It bundles with rules targeted to Docker Best Practices to find security and maintainability problems faster. It is written in pure Kotlin and utilizes the features of the IntelliJ platform. All of this makes shift-left happen due to the fly checks. If you are interested in protecting a Kubernetes cluster, read my article about "&lt;a href="https://protsenko.dev/kubernetes-security-top-12-best-practices-to-protect-your-cluster/" rel="noopener noreferrer"&gt;Kubernetes Security: Best Practices to Protect Your Cluster&lt;/a&gt;".&lt;/p&gt;

&lt;p&gt;Don’t miss my new articles—follow me on &lt;a href="https://www.linkedin.com/in/protsenkodev/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>kubernetes</category>
      <category>devops</category>
    </item>
    <item>
      <title>Kubernetes Security: Best Practices to Protect Your Cluster</title>
      <dc:creator>Dmitry Protsenko</dc:creator>
      <pubDate>Tue, 16 Sep 2025 19:33:09 +0000</pubDate>
      <link>https://dev.to/protsenko/kubernetes-security-best-practices-to-protect-your-cluster-fcm</link>
      <guid>https://dev.to/protsenko/kubernetes-security-best-practices-to-protect-your-cluster-fcm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;You can find the original version of this article on my website, &lt;a href="https://protsenko.dev/kubernetes-security-top-12-best-practices-to-protect-your-cluster/" rel="noopener noreferrer"&gt;protsenko.dev&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hi! In this article, I'm sharing 12 collected Kubernetes security best practices for making your cluster secure by writing secure deployments/services, etc. This article is based on my experience creating the &lt;a href="https://plugins.jetbrains.com/plugin/25413-cloud-iac-security" rel="noopener noreferrer"&gt;Kubernetes Security IDEA plugin&lt;/a&gt; and all of the practices covered by the plugin.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If this project has been helpful to you, please consider giving it a ⭐ on &lt;a href="https://github.com/NordCoderd/cloud-security-plugin" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; to help others discover it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  12 Kubernetes Hardening Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Using Non-Root Containers
&lt;/h3&gt;

&lt;p&gt;Always try to &lt;strong&gt;run containers as a non-root user&lt;/strong&gt;. By default, containers execute as the &lt;code&gt;root&lt;/code&gt; user (UID 0) inside the container, unless the image or Kubernetes securityContext specifies otherwise. If you run a container as root, it might seem harmless. If an attacker breaks out of the container, they will have root on the host. Even without a full breakout, a process running as root with certain misconfigurations (like some of the above capabilities or host mounts) could do more damage to the host. There’s a lack of certain preventive security controls when running as root, which &lt;strong&gt;increases the risk of container escape&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The better practice is to run as an unprivileged user. You can create a user in your container image (many official images have a user like &lt;code&gt;node&lt;/code&gt;, &lt;code&gt;nginx&lt;/code&gt;, etc.) Then either set that as the default in the Dockerfile or use Kubernetes to request it. Kubernetes securityContext has fields &lt;code&gt;runAsUser&lt;/code&gt; and &lt;code&gt;runAsNonRoot&lt;/code&gt; which helps enforce this. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runAsUser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;      &lt;span class="c1"&gt;# UID 1000 (non-root user)&lt;/span&gt;
  &lt;span class="na"&gt;runAsNonRoot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;   &lt;span class="c1"&gt;# Ensure the container will not start as root&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By specifying &lt;code&gt;runAsNonRoot: true&lt;/code&gt;, the kubelet will actually refuse to start the container if it would run as UID 0. This is a guardrail in case someone tries to deploy an image that runs as root – it won’t run unless you explicitly allow root.&lt;/p&gt;

&lt;p&gt;If you have an image that &lt;em&gt;must&lt;/em&gt; run as root (some older software might assume it, or it needs privileged access), think carefully – can you modify the image or use a different solution? Running as root should be the exception, not the norm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Many base images provide a non-root user, but don’t activate it by default. For example, the official Node.js image has a user “node” (UID 1000). You can use that in Kubernetes by doing runAsUser: 1000. For images that lack a user, consider rebuilding the image to add one or switching to an image that supports non-root operation.&lt;/p&gt;

&lt;p&gt;Running as non-root adds an extra layer to Kubernetes security. Even if an attacker gets code execution in the container, they hit a lower-privileged user boundary, and it’s harder to escalate from there. Combine this with not running privileged and dropping caps, and your container is much less attractive to attackers.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Using Privileged Containers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Don’t run containers in privileged mode&lt;/strong&gt; unless absolutely necessary (and practically, it’s almost never necessary for typical apps). A privileged container (&lt;code&gt;securityContext.privileged: true&lt;/code&gt;) has &lt;strong&gt;nearly all the same access to the host as processes on the host do&lt;/strong&gt;. It lifts most of the restrictions that containers normally have. When you run a container privileged, it can access devices on the host, and it can become almost indistinguishable from a host process. Privileged mode will share the host’s namespaces (IPC, PID, etc.), and disable many other controls (seccomp, AppArmor, capabilities limits). In essence, a privileged container is “just a process on the host with root privileges,” which negates the security benefits of using containers.&lt;/p&gt;

&lt;p&gt;Someone could use privileged mode for low-level system tasks (for example, a container that needs to manipulate network interfaces or administer the host). But even in those cases, modern Kubernetes has alternatives (like using specific capabilities, or running as a daemon on the host outside of Kubernetes). Granting full privilege is like handing the keys to your kingdom to that container. If compromised, the attacker will trivially root the node and possibly move laterally in the cluster.&lt;/p&gt;

&lt;p&gt;Kubernetes’s baseline policy &lt;strong&gt;forbids privileged containers for general workloads&lt;/strong&gt;. Tools like admission controllers or Pod Security Policies (in the past) would prevent you from deploying privileged pods in most namespaces, and for good reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example to avoid:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;privileged&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see that in a manifest, think twice. Why do you need these rights? Can we instead just give it the specific capability it needs? Or run it differently? Containers are using privileged mode for cluster infrastructure components, not for user applications. However, running something with elevated privileges is a bad idea, not only for Kubernetes security&lt;/p&gt;

&lt;p&gt;If you absolutely must run something privileged (say, a CSI driver or a networking plugin that must manipulate the host network stack), isolate it to its own namespace and prevent untrusted users from deploying containers there. By avoiding privileged mode, you retain the isolation mechanisms (like cgroups, seccomp, AppArmor, namespaces, capabilities restrictions) that make containers a secure way to deploy applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Do not use hostPath Volumes
&lt;/h3&gt;

&lt;p&gt;Avoid hostPath volumes in your Pods whenever possible. A &lt;code&gt;hostPath&lt;/code&gt; volume mounts a file or directory from the host node’s filesystem directly into a pod. This is essentially giving the container direct access to part of the host’s file system. The security implications are significant: if an attacker compromises the container, they could read or modify critical files on the host through the hostPath mount. Even if the container isn’t running as root, an attacker can combine hostPath with other escalations (like running privileged or a sticky bit attack) to tamper with the node.&lt;/p&gt;

&lt;p&gt;HostPath volumes &lt;strong&gt;“present security risks that could lead to container escape.”&lt;/strong&gt; They break the isolation between your application and the host OS. For example, consider if you mount &lt;code&gt;/var/run/docker.sock&lt;/code&gt; from the host (a common but extremely risky practice) – the container can then control the Docker daemon and effectively gain root on the host. Even mounting something innocuous, like &lt;code&gt;/var/log&lt;/code&gt; could allow a malicious container to poison logs or consume disk space. Writing to any hostPath with system files could potentially crash the node or alter its state.&lt;/p&gt;

&lt;p&gt;Kubernetes acknowledges this risk: PodSecurity &lt;em&gt;Restricted&lt;/em&gt; forbids hostPath volumes entirely. If you &lt;em&gt;must&lt;/em&gt; use a hostPath (for example, some daemon needs to read a host file), consider making it read-only and limiting the path as much as possible. Also, run that pod with the least privileges (non-root user, no extra caps, not privileged).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example to avoid:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host-files&lt;/span&gt;
  &lt;span class="na"&gt;hostPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Directory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above would give the container access to the host’s &lt;code&gt;/etc&lt;/code&gt; directory – clearly a bad idea, as it could read passwords or modify config. If your workload needs to read host info, see if there’s an API or Kubernetes mechanism (like Downward API for some metadata) instead.&lt;/p&gt;

&lt;p&gt;There are a few legitimate use cases for hostPath (like a log collection agent reading &lt;code&gt;/var/log/&lt;/code&gt; or a storage plugin writing to a host directory), but those should be deployed with tight controls and usually in dedicated namespaces. For most apps, you shouldn’t need hostPath at all. Use ConfigMap/Secret for config, EmptyDir for scratch space, and PVC for persistent storage. By avoiding hostPath, you keep the container fully sandboxed from the host’s filesystem, making Kubernetes security better.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Do not use hostPort as Opens the Node’s Port
&lt;/h3&gt;

&lt;p&gt;Be cautious with the &lt;code&gt;hostPort&lt;/code&gt; setting on Pods. When you specify a &lt;code&gt;hostPort&lt;/code&gt; for a container, that port on the Kubernetes node (host machine) is opened and mapped to your pod. This can be risky because it exposes the host’s network interface to the container. If an attacker compromises the container, they could potentially intercept traffic on that host port or exploit it to gain deeper access. Exposing a host port to a container can &lt;strong&gt;open network pathways into your cluster&lt;/strong&gt;, allowing the container to intercept traffic to a host service or bypass network policies. It also constrains scheduling because only one Pod per node can use each host port, and it can lead to port conflicts.&lt;/p&gt;

&lt;p&gt;In general, you should &lt;strong&gt;avoid using hostPort unless absolutely necessary&lt;/strong&gt;. Kubernetes Services (NodePort or LoadBalancer types) or Ingress resources better serve most use cases, such as exposing a service externally, because they handle traffic routing more securely without binding directly to the host’s network ports. Reserve &lt;code&gt;hostPort&lt;/code&gt; for low-level system pods or networking tools that require a specific port on every node, and even then, use it sparingly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example to avoid:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hostport-pod&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;hostPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Do not share the Host Namespace
&lt;/h3&gt;

&lt;p&gt;Pods can request to share certain namespaces with the host (node) – namely, the &lt;strong&gt;network, PID (process), and IPC namespaces&lt;/strong&gt;. When a container shares the host’s namespace, it essentially breaks the isolation between the container and the host for that aspect. &lt;strong&gt;You should avoid it for most workloads.&lt;/strong&gt; For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If a pod sets &lt;code&gt;hostNetwork: true&lt;/code&gt;, it means the pod is using the host machine’s network interface directly. The pod can see all host network interfaces and even potentially sniff traffic. This breaks the default network isolation between pods and the host.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;hostPID: true&lt;/code&gt;, the container shares the host’s process ID space. That means it can see (and potentially interact with) processes running on the host (or other pods on the host). An attacker might leverage this to tamper with host processes or simply gather sensitive info.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;hostIPC: true&lt;/code&gt;, the pod shares the host’s inter-process communication namespace (things like shared memory segments). That could allow a malicious container to read/write shared memory used by something on the host.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, &lt;strong&gt;sharing host namespaces&lt;/strong&gt; can lead to a container escape, compromising Kubernetes security. Pods that share namespaces with the host can communicate with host processes and glean information about the host, which is why baseline security policies disallow it.&lt;/p&gt;

&lt;p&gt;Unless you are running a system-level daemon that &lt;em&gt;needs&lt;/em&gt; this (e.g., a monitoring agent that needs to see all host processes, or a network plugin that needs host networking), you should leave these fields false (which is the default).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example to avoid:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hostNetwork&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;hostPID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;hostIPC&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of those should normally be false or not set at all. If you need one of them for a specific reason (say, &lt;code&gt;hostNetwork&lt;/code&gt; for a networking pod), isolate that to a dedicated namespace or node and tightly control it. And never run general application pods with any of those enabled.&lt;/p&gt;

&lt;p&gt;Kubernetes Pod Security Standards (&lt;a href="https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted" rel="noopener noreferrer"&gt;Baseline&lt;/a&gt; and &lt;a href="https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted" rel="noopener noreferrer"&gt;Restricted&lt;/a&gt;) &lt;strong&gt;disallow sharing host namespaces&lt;/strong&gt; for exactly these reasons. Adhere to that: &lt;strong&gt;pods should live in their own namespaces, not the host’s.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Do not use Insecure Capabilities
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Drop unnecessary Linux capabilities from your containers.&lt;/strong&gt; By default, containers run with a limited set of Linux capabilities – these are like fine-grained permissions that the root user inside the container can have. Granting additional or “non-default” capabilities can be dangerous. Certain powerful capabilities (for example, &lt;code&gt;SYS_ADMIN&lt;/code&gt; or &lt;code&gt;NET_ADMIN&lt;/code&gt;) can allow a process in a container to perform actions that might lead to container escapes or privilege escalations on the node. As Google’s GKE security guidance notes: giving a container extra capabilities could allow it to break out of the container sandbox.&lt;/p&gt;

&lt;p&gt;If you don’t explicitly drop capabilities, a container still has a small set of default capabilities. For better security, it’s best practice to &lt;strong&gt;drop all capabilities and only add back what you truly need&lt;/strong&gt;. This adheres to the principle of least privilege. Kubernetes lets you specify this in the pod or container security context. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALL"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NET_BIND_SERVICE"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above snippet, we drop everything and then only add &lt;code&gt;NET_BIND_SERVICE&lt;/code&gt; (which allows binding to ports below 1024) as an example of a minimally required capability. The Kubernetes &lt;strong&gt;Restricted&lt;/strong&gt; policy profile actually expects that containers drop ALL capabilities and, at most, add only a very limited set, like &lt;code&gt;NET_BIND_SERVICE&lt;/code&gt;. Many common containers (especially web apps) do not require any special Linux capabilities to function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example to avoid:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NET_RAW"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SYS_ADMIN"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;NET_RAW&lt;/code&gt; allows the container to create raw sockets (which could be abused for packet spoofing or sniffing), and &lt;code&gt;SYS_ADMIN&lt;/code&gt; is an extremely privileged capability that [among other things] allows mounting file systems, configuring network interfaces, etc. An attacker could use these to escape the container and damage Kubernetes security.&lt;/p&gt;

&lt;p&gt;If your application truly needs a specific capability, add &lt;em&gt;only that one&lt;/em&gt; and carefully audit the implications. In general, try to run with as few capabilities as possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. AppArmor Profile Disabled or Overridden
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Avoid disabling or overriding AppArmor profiles for your containers.&lt;/strong&gt; AppArmor is a Linux kernel security module that can confine what a container can do at the system level. On AppArmor-supported hosts, Kubernetes applies a default profile (&lt;code&gt;runtime/default&lt;/code&gt;) to containers, which restrict certain actions. If you run a pod with an &lt;strong&gt;unconfined&lt;/strong&gt; AppArmor profile, you are effectively turning off these protections. In fact, experts consider running a container with an unconfined AppArmor profile a &lt;strong&gt;bad security practice&lt;/strong&gt;. It means AppArmor doesn't restrict the container at all, which increases the potential damage if an attacker compromises that container.&lt;/p&gt;

&lt;p&gt;By default, if you don’t specify an AppArmor profile, the container runtime’s default policy is used (which is typically a reasonably safe profile that provides essential Kubernetes Security protection). You should &lt;strong&gt;not&lt;/strong&gt; explicitly set the profile to &lt;code&gt;unconfined&lt;/code&gt; (which disables AppArmor). Instead, allow the default or use a tailored profile if you have one. &lt;a href="https://kubernetes.io/docs/concepts/security/pod-security-standards" rel="noopener noreferrer"&gt;Kubernetes Pod Security&lt;/a&gt; policies recommend using either the runtime default or specific allowed profiles, and preventing any override to an unconfined state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example to avoid:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;insecure-pod&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container.apparmor.security.beta.kubernetes.io/my-container&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unconfined&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-container&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above snippet, the annotation forces the container’s AppArmor profile to &lt;strong&gt;unconfined&lt;/strong&gt;, disabling AppArmor confinement. Instead, you should either omit the annotation (to use the default profile) or set it to &lt;code&gt;runtime/default&lt;/code&gt; (to explicitly use the default). This ensures AppArmor is enforcing some restrictions on what the container can access on the host.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Do not override Non-Default /proc Mount
&lt;/h3&gt;

&lt;p&gt;Ensure your containers use the &lt;strong&gt;default /proc mount behavior&lt;/strong&gt;. In Linux, &lt;code&gt;/proc&lt;/code&gt; it is a virtual filesystem that exposes process and kernel information. Container runtimes normally mask or hide certain paths &lt;code&gt;/proc&lt;/code&gt; to prevent containers from seeing sensitive host information. Kubernetes has a setting &lt;code&gt;procMount&lt;/code&gt; in the security context that can be either &lt;code&gt;Default&lt;/code&gt; (the normal, masked behavior) or &lt;code&gt;Unmasked&lt;/code&gt;. &lt;strong&gt;Do not use&lt;/strong&gt; &lt;code&gt;Unmasked&lt;/code&gt; for &lt;code&gt;procMount&lt;/code&gt; unless you have a very good reason. An “unmasked” /proc means the container can see a lot more system info, which can lead to information leakage or even assist in a container escape.&lt;/p&gt;

&lt;p&gt;Deployments with an &lt;strong&gt;unsafe /proc mount (procMount=Unmasked)&lt;/strong&gt; bypass the default kernel protections. An unmasked &lt;code&gt;/proc&lt;/code&gt; can potentially expose host information to the container, resulting in information leaks or providing an avenue for attackers to escalate privileges. For example, an Unmasked /proc might reveal details of processes running on the host or allow access to &lt;code&gt;/proc/kcore&lt;/code&gt; (which could be dangerous). Unless you’re doing low-level debugging or monitoring that explicitly requires this (which is rare and usually better handled another way), you should not change the procMount from its default to maintain strong Kubernetes Security.&lt;/p&gt;

&lt;p&gt;The best practice is simple: &lt;strong&gt;leave procMount as Default&lt;/strong&gt;, which is also the Kubernetes default behavior if you don’t specify it. The &lt;a href="https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted" rel="noopener noreferrer"&gt;Pod Security Restricted standards&lt;/a&gt; require that &lt;code&gt;/proc&lt;/code&gt; the mask remain the default for all containers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example to avoid:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;procMount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unmasked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In summary, &lt;strong&gt;do not unmask /proc&lt;/strong&gt;. Keep the default masks that Kubernetes and the container runtime provide—they reduce the container’s visibility into the host’s processes and kernel.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Do not use Restricting Volume Types
&lt;/h3&gt;

&lt;p&gt;Not all types of volumes are equal when it comes to security. Kubernetes supports many volume types (ConfigMaps, Secrets, persistent volumes, hostPath, NFS, emptyDir, etc.), but some can expose your Pod to risk. The &lt;strong&gt;&lt;a href="https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted" rel="noopener noreferrer"&gt;Pod Security “Restricted” standard&lt;/a&gt;&lt;/strong&gt; defines an allow-list of safe volume types that a pod can use. The Restricted policy allows only the following volume types: ConfigMap, CSI, DownwardAPI, emptyDir, Ephemeral (inline CSI volumes), PersistentVolumeClaim, Projected, and Secret. In practice, these are volumes that do &lt;strong&gt;not&lt;/strong&gt; directly mount the host’s filesystem in an unsafe way.&lt;/p&gt;

&lt;p&gt;The volume types &lt;em&gt;not&lt;/em&gt; on that list (for example, &lt;code&gt;hostPath&lt;/code&gt;, &lt;code&gt;NFS&lt;/code&gt;, &lt;code&gt;awsElasticBlockStore&lt;/code&gt;, and some others) are either inherently risky or are better handled via PersistentVolumeClaims. For instance, a &lt;code&gt;hostPath&lt;/code&gt; volume mounts a directory from the node’s filesystem into your container – this can easily lead to container escapes or tampering with host files (we discuss hostPath in detail in the next section). Using a &lt;code&gt;hostPath&lt;/code&gt; volume mounts a node’s directory into your container, risking container escapes and host file tampering (discussed in the next section). NFS and other network storage volumes present a lower privilege escalation risk, but without proper management, they can enable denial-of-service or data tampering. To prevent this, manage them through the PersistentVolume subsystem instead of directly within a Pod spec.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best practice:&lt;/strong&gt; &lt;strong&gt;Use only the necessary volume types and prefer higher-level abstractions.&lt;/strong&gt; If you need to mount storage, use PersistentVolumeClaim (with a proper StorageClass) instead of directly using a &lt;code&gt;hostPath&lt;/code&gt; or other host-dependent volume. This way, the cluster can enforce storage isolation, and you avoid giving the container direct access to the host. You should pass most config data via ConfigMap or Secret volumes rather than baking it into images or using host paths.&lt;/p&gt;

&lt;p&gt;If you have to enforce this, consider enabling the Kubernetes Pod Security Admission controller in &lt;strong&gt;restricted&lt;/strong&gt; mode for your namespaces. It will automatically forbid Pods that use disallowed volume types. In short, &lt;strong&gt;limit volume types to the safe set&lt;/strong&gt; – basically, ephemeral volumes (emptyDir, etc.), config/secret volumes, and PVCs backed by external storage. This reduces the risk of a container directly accessing the host or other unintended data sources, thereby strengthening Kubernetes Security.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. Do not set Custom SELinux Options
&lt;/h3&gt;

&lt;p&gt;Avoid specifying custom SELinux options for your pods unless you really know what you are doing. &lt;strong&gt;SELinux&lt;/strong&gt; is another Linux kernel security mechanism (a Mandatory Access Control system) that labels resources and defines which processes can access which resources. Kubernetes, by default, will let the container runtime apply a default SELinux context to your container (usually a confined type like &lt;code&gt;container_t&lt;/code&gt;). You have the option to override the SELinux context via the pod or container securityContext (&lt;code&gt;seLinuxOptions&lt;/code&gt; field), but changing these labels can weaken isolation if done incorrectly.&lt;/p&gt;

&lt;p&gt;The Snyk security blog warns: altering the SELinux labels of a container process could potentially allow that process to &lt;strong&gt;escape its container and access the host filesystem&lt;/strong&gt;. In simpler terms, the default SELinux policy on a host prevents containers from seeing or modifying host files. If you override the SELinux type or role to something more privileged (or turn SELinux to permissive mode on the host), a compromised container might break out and read/write host files it shouldn’t.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/security/pod-security-standards" rel="noopener noreferrer"&gt;Kubernetes’ Pod Security Standards&lt;/a&gt; reflect this by restricting SELinux options. The Restricted profile forbids setting a custom SELinux user or role, and only allows specific SELinux types (the standard container types like &lt;code&gt;container_t&lt;/code&gt; or &lt;code&gt;container_init_t&lt;/code&gt;). Unless you have a specific need (for example, integrating with a host that uses SELinux extensively and has custom policies), you typically won’t set these at all. Just let the container runtime apply the default confinement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example to avoid:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;seLinuxOptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system_u&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system_r&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spc_t&lt;/span&gt;      &lt;span class="c1"&gt;# “spc_t” is a special type for super-privileged containers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above would label the container in a very permissive way (depending on host policy, &lt;code&gt;spc_t&lt;/code&gt; might allow broad access). This is &lt;strong&gt;not recommended&lt;/strong&gt; unless absolutely required by your security team’s policy. In most cases, you should &lt;strong&gt;omit seLinuxOptions entirely&lt;/strong&gt;. If you do need it, stick to the container types provided by your distribution (for example, on Red Hat-based systems, &lt;code&gt;container_t&lt;/code&gt; is the confined type for containers).&lt;/p&gt;

&lt;p&gt;In summary, do not override SELinux labels to something less restrictive. The defaults are there to keep your container constrained and maintain Kubernetes Security. Manage SELinux at the cluster/node level rather than per Pod unless you’re an SELinux expert with a clear goal. And if SELinux is too much overhead, consider using AppArmor or seccomp for adding security – but never making the container &lt;em&gt;more&lt;/em&gt; privileged than defaults.&lt;/p&gt;

&lt;h3&gt;
  
  
  11. Left Seccomp Profile by default
&lt;/h3&gt;

&lt;p&gt;Enable a seccomp profile for your containers (or use the default one); do not run containers with seccomp turned off (“unconfined”). &lt;strong&gt;Seccomp&lt;/strong&gt; (secure computing mode) is a Linux kernel feature that can filter system calls that a process is allowed to make. Kubernetes lets you specify a seccomp profile for pods/containers. If you don’t specify anything, historically many runtimes would run the container as unconfined (no filtering), but newer Kubernetes versions and runtimes often apply a default seccomp profile (e.g., Docker’s default seccomp profile) automatically. Regardless, you want seccomp filtering in place.&lt;/p&gt;

&lt;p&gt;Running a container with &lt;strong&gt;seccomp=Unconfined&lt;/strong&gt; means it can call any syscalls it wants, which broadens the attack surface. Unconfined places no restrictions on syscalls – allowing all system calls, which &lt;strong&gt;reduces security&lt;/strong&gt;. In contrast, the default seccomp profile blocks dozens of dangerous syscalls that containers typically never need (like manipulating kernel modules, system clocks, etc.). These blocked calls are often those that could be used to break out or do harm to the host.&lt;/p&gt;

&lt;p&gt;Best practice: use &lt;code&gt;RuntimeDefault&lt;/code&gt; seccomp profile (Kubernetes’ way of saying “use the container runtime’s default seccomp policy”) or a specific custom profile if you have one. The Kubernetes Restricted policy requires that seccomp be explicitly set to either RuntimeDefault or a named profile, and not left as Unconfined to maintain strong Kubernetes Security&lt;/p&gt;

&lt;p&gt;This ensures the container is running under seccomp confinement. If you wanted to use a custom profile, you’d set &lt;code&gt;type: Localhost&lt;/code&gt; and provide the profile file, but that’s an advanced scenario. The main thing is – &lt;strong&gt;don’t set seccompProfile type to Unconfined&lt;/strong&gt;. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;securityContext:
  seccompProfile:
    type: Unconfined
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above would explicitly disable syscall filtering, exposing you to exploits that leverage obscure syscalls. There have been real-world container breakouts that depended on having seccomp off, so turning it on is a simple way to mitigate whole classes of kernel vulnerabilities.&lt;/p&gt;

&lt;p&gt;Unless you have a specific container that is failing due to seccomp (in which case, consider adjusting the profile rather than removing it entirely), you should always use seccomp. It’s a transparent layer of defense with little to no performance cost in typical applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. Beware of using Insecure Sysctls
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Do not enable unsafe sysctls in your Pods.&lt;/strong&gt; Sysctls (system controls) are kernel parameters that can tweak networking, memory, and other settings. Kubernetes classifies sysctls into &lt;em&gt;safe&lt;/em&gt; and &lt;em&gt;unsafe&lt;/em&gt;. Safe sysctls are those that are namespaced to the container or pod (meaning their effects are limited to that pod) and isolated from the host. Unsafe sysctls are those that &lt;strong&gt;apply to the entire host kernel and could affect all pods&lt;/strong&gt; or even compromise security. Examples of unsafe sysctls might include things like &lt;code&gt;kernel.shmmax&lt;/code&gt; (which affects kernel shared memory limits globally) or &lt;code&gt;net.ipv4.ip_forward&lt;/code&gt; (which could change node-level networking behavior).&lt;/p&gt;

&lt;p&gt;Enabling unsafe sysctls can &lt;strong&gt;disable important security mechanisms or negatively impact the node’s stability&lt;/strong&gt;. They might allow a pod to consume resources beyond its limits or interfere with other pods. In the worst case, a bad actor could use an unsafe sysctl to panic the kernel or elevate privileges.&lt;/p&gt;

&lt;p&gt;Kubernetes &lt;em&gt;by default&lt;/em&gt; will prevent pods from using unsafe sysctls unless the cluster admin has explicitly allowed it (there’s a feature gate and a whitelist one can set on the kubelet). The best practice is to stick to the safe sysctls. According to the Pod Security Standards, you should disallow all but an allowed safe subset of sysctls. Safe sysctls include a handful of names like &lt;code&gt;net.ipv4.ip_local_port_range&lt;/code&gt;, &lt;code&gt;net.ipv4.tcp_syncookies&lt;/code&gt;, &lt;code&gt;net.ipv4.ping_group_range&lt;/code&gt;, etc., which are known not to break isolation.&lt;/p&gt;

&lt;p&gt;If you find yourself needing to set a kernel parameter for your application to run, double-check if it’s truly namespaced. For example, increasing &lt;code&gt;kernel.shmmax&lt;/code&gt; for a database – rather use a proper mechanism or ensure it’s allowed, because that setting affects the host kernel’s shared memory allowance for all processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; The following Pod securityContext shows setting a sysctl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;securityContext:
  sysctls:
  - name: kernel.shmmax
    value: "16777216"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This particular sysctl (&lt;code&gt;kernel.shmmax&lt;/code&gt;) is not namespaced per pod – it would raise the shared memory segment size limit on the host kernel itself. This could impact other pods or processes on the host. Such a sysctl is considered unsafe and would be rejected by Kubernetes unless the cluster is configured to allow it (and it generally shouldn’t be). In contrast, a “safe” sysctl like &lt;code&gt;net.ipv4.tcp_syncookies&lt;/code&gt; could be set in a pod’s spec if needed, because it’s isolated to the pod’s network namespace.&lt;/p&gt;

&lt;p&gt;In summary, &lt;strong&gt;avoid using sysctls that are not explicitly documented as safe for Kubernetes&lt;/strong&gt;. If you absolutely require an unsafe sysctl for a specialized application, you’ll need a waiver in the cluster, and you should isolate that workload as much as possible. For everyone else – stick to defaults; don’t turn your pods into mini kernel tweakers. The default kernel settings are usually fine, and if not, they should be tuned on the host by admins, not on a per-pod basis by application owners.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Kubernetes Security Action Plan
&lt;/h2&gt;

&lt;p&gt;By adhering to these 12 Kubernetes security best practices, you significantly harden your Kubernetes cluster’s security. Many of these boil down to &lt;strong&gt;least privilege&lt;/strong&gt; – giving your pods only the access they truly need and nothing more.&lt;/p&gt;

&lt;p&gt;It’s a good idea to integrate these checks into your development and CI/CD process. For the best IDE integration, give the &lt;a href="https://plugins.jetbrains.com/plugin/25413-cloud-iac-security/" rel="noopener noreferrer"&gt;Kubernetes Security plugin for JetBrains IDEs&lt;/a&gt; a try. It is written in pure Kotlin and utilizes the features of the IntelliJ platform. All of this makes shift-left happen due to the fly checks. If you are interested in IDE plugin development, read my article about "&lt;a href="https://protsenko.dev/2025/03/24/how-i-made-docker-linter-for-intellij-idea-and-other-jetbrains-ide/" rel="noopener noreferrer"&gt;How I made Docker linter for IntelliJ IDEA&lt;/a&gt;"&lt;/p&gt;

&lt;p&gt;Don’t miss my new articles—follow me on &lt;a href="https://www.linkedin.com/in/protsenkodev/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>containers</category>
      <category>cybersecurity</category>
      <category>docker</category>
    </item>
    <item>
      <title>Using SBOMs to detect possible Dependency Confusion</title>
      <dc:creator>Dmitry Protsenko</dc:creator>
      <pubDate>Fri, 15 Aug 2025 20:08:20 +0000</pubDate>
      <link>https://dev.to/protsenko/using-sboms-to-detect-possible-dependency-confusion-5846</link>
      <guid>https://dev.to/protsenko/using-sboms-to-detect-possible-dependency-confusion-5846</guid>
      <description>&lt;p&gt;Software supply chains have become a focal point for attackers, as modern applications rely heavily on third-party and open-source dependencies. Organizations are adopting Software Bill of Materials (SBOM) documents to gain visibility into their software components. In this article, we explore SBOMs, why the CycloneDX format is recommended (and how it compares to SPDX), and how an SBOM can be leveraged to detect and prevent &lt;a href="https://protsenko.dev/2025/04/30/dependency-confusion-detection-mitigation-2025/" rel="noopener noreferrer"&gt;dependency confusion&lt;/a&gt; in your projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is SBOM?
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Software Bill of Materials (SBOM)&lt;/strong&gt; is a detailed, formally structured inventory of all components that make up a software product. It lists all libraries, packages, and modules — open-source and proprietary — and includes metadata such as versions, licenses, hashes, and dependency relationships. An SBOM enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vulnerability management&lt;/strong&gt; — quickly identify components with known security flaws&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License compliance&lt;/strong&gt; — track and verify open-source license obligations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supply chain transparency&lt;/strong&gt; — improve auditability and trust in software&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Several standards exist for representing SBOMs, the most common being &lt;strong&gt;SPDX&lt;/strong&gt; and &lt;strong&gt;CycloneDX&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Use CycloneDX for SBOMs?
&lt;/h3&gt;

&lt;p&gt;CycloneDX and SPDX are both industry-approved SBOM formats with distinct strengths:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Primary focus&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;CycloneDX&lt;/em&gt;: Application security, vulnerability workflows, and supply-chain risk analysis&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;SPDX&lt;/em&gt;: License compliance, provenance, and legal auditing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Metadata depth&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;CycloneDX&lt;/em&gt;: Detailed component relationships, Vulnerability Exploitability eXchange (VEX) data, hashes, and custom metadata&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;SPDX&lt;/em&gt;: Comprehensive license expressions, file checksums, and package provenance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Schema and tool support&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;CycloneDX&lt;/em&gt;: Lightweight JSON/XML, optimized for security tooling (Trivy, Syft, Dependency-Track)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;SPDX&lt;/em&gt;: RDF/JSON, widely adopted in open-source communities and legal workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Extensibility&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;CycloneDX&lt;/em&gt;: VEX (Vulnerability Exploitability eXchange), attestations, and custom data fields&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;SPDX&lt;/em&gt;: Provenance tracking, license exception clauses, and rich license metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ecosystem adoption&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;CycloneDX&lt;/em&gt;: Rapid growth across DevSecOps pipelines, strong support for automated security scans&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;SPDX&lt;/em&gt;: Established in open-source governance, favored for compliance and audit use cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why prefer CycloneDX?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built with security use cases in mind: supports vulnerability (VEX) and attestation data.&lt;/li&gt;
&lt;li&gt;Simplified data model: easier integration into CI/CD and parsing by security tools.&lt;/li&gt;
&lt;li&gt;Rapidly growing ecosystem: many scanners and platforms generate or consume CycloneDX.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, SPDX remains valuable where license compliance is paramount.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Dependency Confusion?
&lt;/h3&gt;

&lt;p&gt;Dependency confusion attacks the supply chain to deliver &lt;a href="https://protsenko.dev/2025/04/21/malicious-packages-in-npm-and-pypi-how-typosquatting-threatens-developers/" rel="noopener noreferrer"&gt;malicious code&lt;/a&gt; into the development environment. Developers use package manager systems to download packages (dependencies) and build projects. Usually, developers add some packages to the build file and initiate installing or downloading those packages. Downloading happens from public repositories, most commonly npmjs.com and PyPI.org. Anyone can register and publish their packages.&lt;/p&gt;

&lt;p&gt;For more details, see my earlier article: &lt;a href="https://protsenko.dev/2025/04/30/dependency-confusion-detection-mitigation-2025/" rel="noopener noreferrer"&gt;Dependency Confusion Detection &amp;amp; Mitigation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using SBOM to Detect Dependency Confusion
&lt;/h3&gt;

&lt;p&gt;An SBOM enables a systematic check for name collisions between internal dependencies and public registries:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inventory&lt;/strong&gt;: Generate an SBOM listing all dependencies (including private/internal ones).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identify internals&lt;/strong&gt;: Determine which components should be internal based on naming conventions or repository metadata.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-check&lt;/strong&gt;: Query public package registries (e.g., PyPI, npm) for each internal package name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flag collisions&lt;/strong&gt;: Any internal name found publicly is a potential dependency confusion risk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review and remediate&lt;/strong&gt;: Confirm the legitimacy of public packages, tighten registry configurations, reserve names, or rename internal packages.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  SBOM Components and PURLs (Package URLs)
&lt;/h3&gt;

&lt;p&gt;CycloneDX defines &lt;strong&gt;components&lt;/strong&gt; (dependencies) and uses &lt;strong&gt;Package URLs (purls)&lt;/strong&gt; — a standardized identifier — to describe each component. A purl has the format:&lt;/p&gt;

&lt;p&gt;pkg://@&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;pkg:npm/&lt;a href="mailto:lodash@4.17.21"&gt;lodash@4.17.21&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PURLs enable automated registry lookups by indicating the ecosystem (&lt;code&gt;npm&lt;/code&gt;, &lt;code&gt;pypi&lt;/code&gt;, etc.), package name, and version. Parsing purls is the first step in programmatically checking registry existence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating SBOMs with Trivy
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/aquasecurity/trivy" rel="noopener noreferrer"&gt;Trivy&lt;/a&gt; is a widely adopted vulnerability scanner that also generates CycloneDX SBOMs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scan filesystem/project: &lt;code&gt;trivy fs --format cyclonedx -o sbom.json ./&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Scan container image: &lt;code&gt;trivy image --format cyclonedx -o sbom.json alpine:latest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Dedicated SBOM command (recent versions): &lt;code&gt;trivy sbom --output sbom.json ./&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Integrate Trivy into your CI/CD to produce up-to-date SBOMs for every build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Detecting Dependency Confusion with SBOM Analysis
&lt;/h3&gt;

&lt;p&gt;Combine analysis and automation under one workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Parse SBOM JSON&lt;/strong&gt;: Extract the &lt;code&gt;components&lt;/code&gt; array.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract purls&lt;/strong&gt;: For each component, parse the purl to get the ecosystem and name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query public APIs&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PyPI&lt;/strong&gt;: &lt;code&gt;GET https://pypi.org/pypi/&amp;lt;name&amp;gt;/json&lt;/code&gt; → 404 means not found.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;npm&lt;/strong&gt;: &lt;code&gt;GET https://registry.npmjs.org/&amp;lt;name&amp;gt;&lt;/code&gt; → 404 means not found.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Classify results&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not found&lt;/strong&gt;: internal package name unclaimed publicly (reserve name).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Found&lt;/strong&gt;: check if it’s expected (open-source) or unexpected (collision).&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Automate&lt;/strong&gt;: Use a script (e.g., Python) to iterate purls, perform HTTP checks, and generate a report.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This combined approach ensures repeatable, scalable, and CI-integrated detection. The fully automated solution is available in the dedicated &lt;a href="https://github.com/NordCoderd/awesome-software-supply-chain-security/tree/main/dependency-confusion/using-sbom-to-find-dependency-confusion" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Practices for Preventing Dependency Confusion
&lt;/h3&gt;

&lt;p&gt;Drawing on strategies from my &lt;a href="https://protsenko.dev/2025/04/30/dependency-confusion-detection-mitigation-2025/" rel="noopener noreferrer"&gt;Dependency Confusion Detection &amp;amp; Mitigation (April 2025)&lt;/a&gt; article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reserve internal package names&lt;/strong&gt; on public registries by publishing a placeholder or dummy package (prevents attackers from squatting).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use scoped or uniquely namespaced packages&lt;/strong&gt; (e.g., &lt;code&gt;@myorg/internal-lib&lt;/code&gt; on npm) to avoid generic collisions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce registry authentication and pinning&lt;/strong&gt; in build pipelines — limit installs to authenticated private registries when fetching internal dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement periodic SBOM-based audits&lt;/strong&gt; to detect new name collisions and dependency anomalies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor package version inflation&lt;/strong&gt;: if a public package suddenly jumps to a high version, treat it as suspicious.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These practices, linked with SBOM detection workflows, create a robust defense-in-depth against dependency confusion.&lt;/p&gt;

&lt;h3&gt;
  
  
  At the end
&lt;/h3&gt;

&lt;p&gt;SBOMs offer critical transparency into software dependencies. By generating SBOMs in CycloneDX format (e.g., via Trivy) and performing automated analysis of public registry presence, teams can effectively detect and mitigate dependency confusion risks. Expanding SBOM-based checks into CI/CD and adopting best practices — reserving names and enforcing private registry pinning — ensures proactive defense against evolving supply chain attacks. Stay vigilant and integrate SBOM analysis into your security processes to keep your codebase safe.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>supplychainsecurity</category>
      <category>dependencyconfusion</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Dev Diary #2: Cloud Security plugin for JetBrains IDE</title>
      <dc:creator>Dmitry Protsenko</dc:creator>
      <pubDate>Sat, 02 Aug 2025 20:49:35 +0000</pubDate>
      <link>https://dev.to/protsenko/dev-diary-2-cloud-security-plugin-for-jetbrains-ide-3mmo</link>
      <guid>https://dev.to/protsenko/dev-diary-2-cloud-security-plugin-for-jetbrains-ide-3mmo</guid>
      <description>&lt;p&gt;Almost a year passed before I began developing &lt;a href="https://plugins.jetbrains.com/plugin/25413-cloud-iac-security" rel="noopener noreferrer"&gt;the plugin&lt;/a&gt; to improve the security of Infrastructure as Code files. Many rules were implemented, especially for Docker and Dockerfiles, and many lessons &lt;a href="https://protsenko.dev/2025/03/24/how-i-made-docker-linter-for-intellij-idea-and-other-jetbrains-ide/" rel="noopener noreferrer"&gt;were learned&lt;/a&gt;. This week, I found new energy to begin delivering the next milestone in the plugin’s lifecycle. I started implementing Kubernetes rules to align with the NSA Kubernetes Hardening Guide.&lt;/p&gt;

&lt;p&gt;I have always postponed implementing rules to analyze YAML files because it was struggling boring. There wasn’t an API to implement it easily – just brutal PSI analyze. I thought so, but then I found useful classes and methods in the YAML plugin and wrote a simple YAML-path engine to find elements in the text more comfortable. This approach helps me rewrite some smell parts in Docker Compose analyze and start working diving deeper in Kubernetes.&lt;/p&gt;

&lt;p&gt;Everything started from another event in my life: I needed to learn more about Kubernetes Security, so I started research on this subject and found Kubescape. This tool contains a lot of rules to analyze the security of Kubernetes objects, and it has an implementation for VS Code but not for JetBrains IDE. I thought, “hm, okay, I could implement these rules in my plugin.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kubescape.io/" rel="noopener noreferrer"&gt;Kubescape&lt;/a&gt; uses a library of rules that are implemented with &lt;a href="https://www.openpolicyagent.org/" rel="noopener noreferrer"&gt;Open Policy Agent in Rego rules&lt;/a&gt;. Rego rules could be compiled to WASM, and here was a field for experiments. I tried to use &lt;a href="https://github.com/StyraInc/opa-java-wasm" rel="noopener noreferrer"&gt;opa-java-wasm&lt;/a&gt; to integrate Kubescape rules as is. While I experimented with this approach, I found a &lt;a href="https://github.com/StyraInc/opa-java-wasm/issues/78" rel="noopener noreferrer"&gt;bug&lt;/a&gt; that was fixed by the maintainer. I really appreciate how fast it was fixed.&lt;/p&gt;

&lt;p&gt;There were core problems with that approach. To analyze YAML files, they should be converted to JSON first, and while the user is typing in the YAML document, it could raise serialization errors due to a non-parsable YAML file. This was a tombstone for that, because all implemented inspections should work on the fly without those problems. The next problem is that it is necessary to highlight specific elements in the YAML. With text-based output, it is hard to find which elements should be highlighted, because there were corner cases for that. Code with experiments stored in the dedicated &lt;a href="https://github.com/NordCoderd/cloud-security-plugin/tree/kubernetes-wasm-opa-rules" rel="noopener noreferrer"&gt;branch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After that, I started looking for features that could improve my developer productivity, and I finally successfully implemented the first &lt;a href="https://protsenko.dev/infrastructure-security/non-root-containers/" rel="noopener noreferrer"&gt;Kubernetes rule&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Alongside the new YAML-based rules, I did a rebranding. At the start, the plugin pointed to &lt;strong&gt;Infrastructure Security&lt;/strong&gt;, and it was one of the first names. Now, it is called &lt;strong&gt;Cloud (IaC) Security&lt;/strong&gt;. This name is more laconic, shorter, and modern. I think this name should work better for users who install the plugin by searching in the marketplace.&lt;/p&gt;

&lt;p&gt;The new logo was designed for the plugin. Now it’s a mascot of the plugin—a dog with the name “Jessica”. She was my dog for many years, but then our ways separated. This logo is a copy of her pictures, combined into a cyber style with help of AI and re-drawn by an artist in vector format. This step symbolizes a memory of her.&lt;/p&gt;

&lt;p&gt;My goal is to ship at least one new Kubernetes rule or quick fix every week, so development stays steady and predictable. If you haven’t installed the &lt;a href="https://plugins.jetbrains.com/plugin/25413-cloud-iac-security" rel="noopener noreferrer"&gt;plugin&lt;/a&gt; yet, give it a try—I’d love your feedback. And if you’d like a heads-up whenever a new blog post drops, follow me on LinkedIn: &lt;a href="https://www.linkedin.com/in/protsenkodev/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/protsenkodev/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>cybersecurity</category>
      <category>security</category>
      <category>devsecops</category>
    </item>
    <item>
      <title>Revival Hijacking: How Deleted PyPI Packages Become Threats</title>
      <dc:creator>Dmitry Protsenko</dc:creator>
      <pubDate>Sat, 02 Aug 2025 20:09:10 +0000</pubDate>
      <link>https://dev.to/protsenko/revival-hijacking-how-deleted-pypi-packages-become-threats-13jk</link>
      <guid>https://dev.to/protsenko/revival-hijacking-how-deleted-pypi-packages-become-threats-13jk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;You can find the original version of this article on my website,&lt;/em&gt; &lt;a href="https://protsenko.dev/2025/07/21/revival-hijacking-how-deleted-pypi-packages-become-threats/" rel="noopener noreferrer"&gt;&lt;em&gt;protsenko.dev&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hi guys, recently I continued researching &lt;a href="https://protsenko.dev/2025/04/21/malicious-packages-in-npm-and-pypi-how-typosquatting-threatens-developers/" rel="noopener noreferrer"&gt;malicious dependencies&lt;/a&gt; and &lt;a href="https://protsenko.dev/2025/04/30/dependency-confusion-detection-mitigation-2025/" rel="noopener noreferrer"&gt;dependency confusion&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This article is inspired by the &lt;a href="https://jfrog.com/blog/revival-hijack-pypi-hijack-technique-exploited-22k-packages-at-risk/" rel="noopener noreferrer"&gt;article&lt;/a&gt; from 2024 and raises again concerns about the safety of downloading packages from public repositories without additional controls.&lt;/p&gt;

&lt;p&gt;The pypi.org allows packages to be removed by author and package name, making them publicly available for registration by anyone. By registering formerly popular packages, attackers could gain many downloads of malicious libraries.&lt;/p&gt;

&lt;p&gt;This technique is called revival hijacking. In this article, I look for popular removed packages and exploit this vector to download many stub packages.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Information about deleted and revived packages can be found in the dedicated&lt;/em&gt; &lt;a href="https://github.com/NordCoderd/deleted-pypi-package-index" rel="noopener noreferrer"&gt;&lt;em&gt;GitHub project&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It is updated on a daily schedule to provide fresh lists of these packages and is a result of work on this article.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Harvesting the data
&lt;/h3&gt;

&lt;p&gt;How many packages were deleted from PyPI.org? To answer that question, we need to find out what package names are available currently and what was removed, analyze how many deleted packages were downloaded, and identify who depends on them.&lt;/p&gt;

&lt;p&gt;You need to use the official &lt;a href="https://docs.pypi.org/api/index-api/" rel="noopener noreferrer"&gt;index API&lt;/a&gt; to harvest currently available package names. This API produces all the package names in the format you want. I consumed it in JSON for further processing. This method doesn’t provide 100 percent data validity, but all the “removed” packages could be checked for presence in the public registry.&lt;/p&gt;

&lt;p&gt;The next step is harvesting every package name on PyPI. For this, I used the &lt;a href="https://clickpy.clickhouse.com/" rel="noopener noreferrer"&gt;Clikpy&lt;/a&gt; service, which stores downloads and other valuable analytics about Python packages.&lt;/p&gt;

&lt;p&gt;Clickpy &lt;a href="https://sql.clickhouse.com/?query_id=91KGCVMCD5SRHPWMHGH6UU" rel="noopener noreferrer"&gt;allows&lt;/a&gt; you to perform custom SQL queries on their Clickhouse instance. With that possibility, we could dump a table with PyPI downloads to have information about every package name and even the download count. Download count is worth information because it allows us to define whether a package was popular.&lt;/p&gt;

&lt;p&gt;By default, ClickHouse is limited to querying 100k rows per query, and you need to use pagination to retrieve them all. It’s around eight queries to dump the whole table. You’ve got to install ClickHouse CLI or use another connection method to save results locally.&lt;/p&gt;

&lt;p&gt;After deep research on quality data, I found some package names invalid or trimmed. This means that my results are not 100 percent clean. To get better data, you could retrieve package names via BigQuery tables from &lt;a href="https://docs.deps.dev/bigquery/v1/" rel="noopener noreferrer"&gt;deps.dev&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analyzing collected data
&lt;/h3&gt;

&lt;p&gt;We have all the necessary data for this step. Let’s figure out how many packages were removed and could be squatted.&lt;/p&gt;

&lt;p&gt;The formula to find it is easy: deleted packages = packages from clickpy — packages from pypi. The answer to that question is 91752. This is a really big count of packages that were removed and could be squatted. Let’s aim for only the popular ones.&lt;/p&gt;

&lt;p&gt;It’s better to skip packages that have fewer than a thousand downloads, leaving only 15467 packages with 233 million downloads. This download count is huge, but it’s worth mentioning that this download counter could be faked.&lt;/p&gt;

&lt;p&gt;During data analysis, I had an idea to build graphs on removed packages and their dependents to find something interesting. Speaking of the future, I found those packages. Some of them were valid cases, some of them were not.&lt;/p&gt;

&lt;p&gt;You could collect all the necessary data using the following ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find information on who is dependent on removed packages using clickpy.&lt;/li&gt;
&lt;li&gt;Find information about every published package and its dependencies to find removed dependencies in it. For that, you could use deps.dev or the PyPI API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, I tried using Clickpy, as it provided data about dependent packages that needed to be removed. However, after dumping the necessary information, I found that it was not relevant as it contained data about non-existent dependencies, so the next numbers will be a bit dirty.&lt;/p&gt;

&lt;p&gt;Based on clickpy data, only 520 packages have 1405 dependents, which gives us 228 billion downloads. That means that packages already have a transitive dependency that could be hijacked to deliver malicious code. Some of these removed packages contain prohibited keywords or refer to a Python built-in function and couldn’t be registered.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exploitation
&lt;/h3&gt;

&lt;p&gt;My next stage in the research was publishing stub packages on PyPI. PyPI.org doesn’t allow security research on its platform. During my research, my accounts, which I used for publishing, were disabled, but packages were still accessible. This gap between disabling and removing packages gave me time to collect analytics to present to the public.&lt;/p&gt;

&lt;p&gt;Please do not repeat this activity after me, as it burdens support with detecting and removing stub packages. As a concise security researcher, I contacted support about the future of my accounts and asked them to enable me to remove all the published packages on my side. I promised them not to repeat this activity anymore.&lt;/p&gt;

&lt;p&gt;Okay, let’s go back to the exploitation. I took most downloadable deleted packages. Some of them I validated manually via GitHub search, and even found possibilities of dependency confusion in popular projects that were &lt;a href="https://github.com/EleutherAI/lm-evaluation-harness/pull/3112" rel="noopener noreferrer"&gt;fixed&lt;/a&gt; after messaging them. Some of them I took only by their download count without validation.&lt;/p&gt;

&lt;p&gt;You could publish only &lt;code&gt;20&lt;/code&gt; packages in an hour, so with this limit, I was able to publish 168 packages from two accounts. Those squatted packages had 45 million downloads in the past; it’s a huge drop, and someone probably will download these squatted packages.&lt;/p&gt;

&lt;p&gt;Publishing was iterative and not consistent. Research continued for one week after the first batch of packages was published and ended with the removal of packages. During the research, squatted packages were downloaded 32,036 times.&lt;/p&gt;

&lt;p&gt;There were leaders in downloads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;febolt — 3056&lt;/li&gt;
&lt;li&gt;flatpack — 2456&lt;/li&gt;
&lt;li&gt;chiquito — 1097&lt;/li&gt;
&lt;li&gt;spl-transpiler — 750&lt;/li&gt;
&lt;li&gt;chalk-harness — 605&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the published packages for installation raised the following error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Installation terminated!&lt;br&gt;&lt;br&gt;
This is a stub package intended to mitigate the risks of dependency confusion.&lt;br&gt;&lt;br&gt;
It holds a once-popular package name removed by its author (or for other reasons, such as security).&lt;br&gt;&lt;br&gt;
This is package not intended to be installed and highlight problems in your setup.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Read more:&lt;/em&gt; &lt;a href="https://protsenko.dev/dependency-confusion" rel="noopener noreferrer"&gt;&lt;em&gt;https://protsenko.dev/dependency-confusion&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The last link refers to the article that describes &lt;a href="https://protsenko.dev/2025/04/30/dependency-confusion-detection-mitigation-2025/" rel="noopener noreferrer"&gt;dependency confusion&lt;/a&gt; attacks and ways to mitigate them. This page received eight times more views than before. Real users came to the page to read more about this problem.&lt;/p&gt;

&lt;p&gt;The number of downloads was insane. Due to a lack of moderation and the possibility of squatting removed packages, users who installed the stub package could be infected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mitigating the problem
&lt;/h3&gt;

&lt;p&gt;How to avoid? Dependencies should be proactively scanned for presence in public repositories. If your projects use internal package repositories with mirrored packages, those packages could be validated against PyPI.org or deps.dev for existence.&lt;/p&gt;

&lt;p&gt;If you find one of these packages, you should deal with it or check its correctness using internal repositories to prevent downloading a higher version from the public.&lt;/p&gt;

&lt;p&gt;For the source information about deleted or revived packages, you could use: package name lists from the &lt;a href="https://github.com/NordCoderd/deleted-pypi-package-index" rel="noopener noreferrer"&gt;Deleted &amp;amp; Revived PyPI Package Indexes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Removed packages from PyPI.org have become critical problems as they could be used to deliver malicious code directly into a closed environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  At the end
&lt;/h3&gt;

&lt;p&gt;It was a pleasant journey to play with data from Clickhouse, BigQuery instances, and PyPI along with deps.dev API. There are more vectors for the attacks, which should be investigated, but for now, the article is ending.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://www.linkedin.com/in/protsenkodev/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; to learn about new articles. Links are in the footer of the page.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>python</category>
      <category>programming</category>
      <category>security</category>
    </item>
    <item>
      <title>Dev Diary #1 – Google Agent Development Kit: Lessons I Learned</title>
      <dc:creator>Dmitry Protsenko</dc:creator>
      <pubDate>Mon, 05 May 2025 16:00:00 +0000</pubDate>
      <link>https://dev.to/protsenko/dev-diary-1-google-agent-development-kit-lessons-i-learned-16mi</link>
      <guid>https://dev.to/protsenko/dev-diary-1-google-agent-development-kit-lessons-i-learned-16mi</guid>
      <description>&lt;p&gt;This diary-tutorial hybrid tracks my first months with &lt;strong&gt;Google’s Agent Development Kit (ADK)&lt;/strong&gt; — pure experience, insight sharing, and nothing else. The &lt;strong&gt;ADK&lt;/strong&gt; is Google’s open-source framework for designing, chaining, and shipping autonomous AI agents. If you are unfamiliar with this framework, read my &lt;a href="https://protsenko.dev/2025/04/23/building-ai-agents-to-prioritize-cves-a-google-adk-guide/" rel="noopener noreferrer"&gt;beginner guide&lt;/a&gt; to build AI agents quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons I learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You don’t need many AI agents, one for each task&lt;/strong&gt;. An agent can call as many tools as it needs. AI independently decides what tool it should use and when. Write a precise prompt and correctly declare what tools AI can access and when it should be used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You should care what you save in the session state.&lt;/strong&gt; AI agents could save the output to the session state, and different agents in your AI pipeline could access it. While developing, you should navigate to the browser to check if the AI agent correctly saved information in the context. If the context is garbage, every later step is garbage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You should correctly work with the session state.&lt;/strong&gt; Defining the output key for an agent will save only the AI agent response in the session state using this key. Even if you’re writing in the prompt instructions like save A to state B, it won’t be saved. Another problem with working with the session state is if you instruct the AI agent to use data from the session state, but data is missing in the session, AI will use something and not raise an error, hiding the problem somewhere deep.&lt;/p&gt;

&lt;p&gt;For a basic example, one agent used a tool to generate numbers in the session context, and another returned a generated number from the session context. What could go wrong?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SequentialAgent&lt;/span&gt;

&lt;span class="n"&gt;GEMINI_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash-preview-04-17&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_random_number&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;agent_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;random_number_provider_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GEMINI_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;output_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;random_number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Return random number between 0 and 100 using get_random_number.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;get_random_number&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;agent_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;random_number_consumer_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Return random number between 0 and 100 from session state under key random_number.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GEMINI_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;root_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SequentialAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sequential_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sub_agents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;agent_A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent_B&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F26ezilsntg3db19ei54w.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F26ezilsntg3db19ei54w.webp" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the agent works as expected. The number was generated and returned. Let’s see what happens if we remove the first agent that generated the number. Reminder: Agent B expects &lt;code&gt;random_number&lt;/code&gt; in state. As Agent A was removed, there is no data in the session.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0lf0til7b6o92k70out.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0lf0til7b6o92k70out.webp" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agent returned the number and pretended the data was in the state, but it wasn’t. This is a fundamental problem – AI agents could hallucinate and lie to us, deeply making our output from the first steps invalid. You could pre-prompt additional validation, but you should do that each time you use the session state to share data with other agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing prompts is challenging.&lt;/strong&gt; Sometimes, I spend many hours doing simple tasks that I could code with Kotlin in 10 minutes. Output with AI agents could sometimes be unpredictable, as classic programming tools have determined behavior, but AI agents have not. You should carefully instruct AI agents on acting in different cases with corner cases, as you do with classic tools.&lt;/p&gt;

&lt;p&gt;You could ask what the problem is, prompting one more skill you should have. Yes, I know it, but there are no automatic tools to validate your prompts as syntax checkers in IDE, just plain text in English and nothing else. No modern linters, static analysis, no validations, and covering each AI prompt function with unit tests to make results predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  At the end
&lt;/h2&gt;

&lt;p&gt;I’m not an AI agent expert. I’m just a software engineer passionate about technology. I find AI agent development exciting, but sometimes I struggle. This is my first step in this field, and I hope to share more insights with you.&lt;/p&gt;

&lt;p&gt;Please share your comments and feedback; it means a lot to me.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>beginners</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Building AI Agents to Prioritize CVEs — A Google ADK Guide</title>
      <dc:creator>Dmitry Protsenko</dc:creator>
      <pubDate>Wed, 23 Apr 2025 18:19:54 +0000</pubDate>
      <link>https://dev.to/protsenko/building-ai-agents-to-prioritize-cves-a-google-adk-guide-3gcf</link>
      <guid>https://dev.to/protsenko/building-ai-agents-to-prioritize-cves-a-google-adk-guide-3gcf</guid>
      <description>&lt;p&gt;In this story, we will create our first AI agents using &lt;a href="https://google.github.io/adk-docs/get-started/installation/" rel="noopener noreferrer"&gt;Agent Development Kit&lt;/a&gt;. AI agents will be integrated with &lt;a href="https://osv.dev/" rel="noopener noreferrer"&gt;Google OSV&lt;/a&gt;, &lt;a href="https://attack.mitre.org/" rel="noopener noreferrer"&gt;MITRE&lt;/a&gt;, &lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog" rel="noopener noreferrer"&gt;KEV&lt;/a&gt;, and a bit of Google search. AI agents will enrich data about given vulnerabilities with public data from different sources to help prioritize (triage) problems.&lt;/p&gt;

&lt;p&gt;Will these AI agents help prioritize vulnerabilities? In their current state, they probably do not, but they provide summarized information about vulnerabilities that could help you understand how critical they are.&lt;/p&gt;

&lt;p&gt;This story was written for educational purposes and to enjoy learning something new. I assume you have some basic programming skills in Python and are familiar with AI.&lt;/p&gt;

&lt;h4&gt;
  
  
  Preparing Environment
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Download &lt;a href="https://www.jetbrains.com/pycharm/" rel="noopener noreferrer"&gt;PyCharm&lt;/a&gt; to save your nerves with dependency management and save time with built-in local LLM bundled with a paid license.&lt;/li&gt;
&lt;li&gt;Create a Python project with &lt;a href="https://github.com/astral-sh/uv" rel="noopener noreferrer"&gt;UV support&lt;/a&gt;. This helps lock the project’s dependencies, making your build reproducible. You could use anything that you find comfortable.&lt;/li&gt;
&lt;li&gt;Install required dependencies for Google ADK via terminal or user interface in PyCharm. I used the last one.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# installation
pip install google-adk
# verify
pip show google-adk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Defining Project Structure
&lt;/h4&gt;

&lt;p&gt;The project structure is important for Google ADK. The root directory will contain a file with an environment to store API keys and a folder for each agent, the folder name being the agent name.&lt;/p&gt;

&lt;p&gt;Your project structure should be in the following format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project_folder/
    vulnerability_agent/
        __init__.py # just initializer
        agent.py # your agent logic will be here
    .env # place for storing API key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need an API key to use Google Models. What you should do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get a key from &lt;a href="https://aistudio.google.com/apikey" rel="noopener noreferrer"&gt;Google AI Studio&lt;/a&gt; — &lt;a href="https://aistudio.google.com/plan_information" rel="noopener noreferrer"&gt;it is free&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Open the &lt;code&gt;&amp;lt;strong&amp;gt;.env&amp;lt;/strong&amp;gt;&lt;/code&gt; file located inside in &lt;code&gt;project_folder&lt;/code&gt; and copy-paste the following code.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GOOGLE_GENAI_USE_VERTEXAI=FALSE
GOOGLE_API_KEY=PASTE_YOUR_ACTUAL_API_KEY_HERE # &amp;lt;- past your API key here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Building Agent
&lt;/h4&gt;

&lt;p&gt;Let’s start with foundational knowledge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An Agent&lt;/strong&gt; in Google ADK is an instance of a class with a name, instructions, and additional tools that they could use. To create an agent, create an instance of the class (LlmAgent), name it &lt;strong&gt;root_agent&lt;/strong&gt;, and start the web console with a simple command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;adk web # for web ui (i prefer this)
adk run vulnerability_agent # for cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# agent.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LlmAgent&lt;/span&gt;
&lt;span class="n"&gt;root_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;i_am_just_agent_smith&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GEMINI_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        You are a lazy AI agent.
        Just do nothing.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# __init__.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Defining Agents Requirements
&lt;/h4&gt;

&lt;p&gt;Ok, this was so easy. Let’s define our requirements and the AI agent’s logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We should collect and save information from three different APIs in the session.&lt;/li&gt;
&lt;li&gt;We should find some information on Google.&lt;/li&gt;
&lt;li&gt;We should summarize all the given information and provide feedback.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Using Tools
&lt;/h4&gt;

&lt;p&gt;Agents could use tools. Tools are Python functions that agents could use. They are different, but I will use &lt;a href="https://google.github.io/adk-docs/tools/function-tools/" rel="noopener noreferrer"&gt;Function&lt;/a&gt; and &lt;a href="https://google.github.io/adk-docs/tools/built-in-tools/" rel="noopener noreferrer"&gt;Built-in tools&lt;/a&gt; in this guide.&lt;/p&gt;

&lt;p&gt;They are just simple functions defined in the logic. No more hidden definitions. Do you want to know what Built-in tools are? They are already included in ADK!&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating Function Tools
&lt;/h4&gt;

&lt;p&gt;You could use tool output in your Agent instructions to save, summarize, or do whatever you want; in my case, I will save it in session context.&lt;/p&gt;

&lt;p&gt;My tools will retrieve data from specified endpoints to enrich data about vulnerabilities; the above is just an example of integrating Google OSV. It is nothing special; retrieving data from API endpoints is monotonous.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# our perfect, clear, reliable function tool
# one API call to rule them (who?) all
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_vulnerability_info_from_osv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vulnerability_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.osv.dev/v1/vulns/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;vulnerability_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error_message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Information about &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;vulnerability_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is not available.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;osv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;## there is could be two more functions but they quite bit same
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s see how I describe my new agent and what is happening inside. Some examples were provided previously, but important details:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Our agent has an identity: &lt;em&gt;You’re a cybersecurity expert specializing in supply chain vulnerabilities&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Our agent has actions: &lt;em&gt;Collect something&lt;/em&gt; using &lt;em&gt;some_tool&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Our agent knows what he should get as a vulnerability name to call the function properly&lt;/li&gt;
&lt;li&gt;Our agent saves information in the session state&lt;/li&gt;
&lt;li&gt;The agent has defined a property named &lt;em&gt;tools&lt;/em&gt; with the names of our methods&lt;/li&gt;
&lt;li&gt;The agent has an output key to store the information, but I duplicate it in the instructions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One fun note: You couldn’t mix built-in and function tools, it raise error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cve_information_provider_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cve_information_provider_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GEMINI_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    You&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;re a cybersecurity expert specializing in supply chain vulnerabilities.
    For vulnerability prioritization you should use the following rules:
    - Collect information about vulnerability using get_vulnerability_info_from_osv tool.
    - Collect information about vulnerability using get_vulnerability_info_from_cve tool.
    - Collect information about known exploited vulnerabilities using check_in_known_exploited_vulnerabilities tool.

    Use the vulnerability name provided as an input. Vulnerability name usually starts with CVE- prefix.

    Save information in the session state under the key &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cve_information&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;get_vulnerability_info_from_osv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;get_vulnerability_info_from_cve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;check_in_known_exploited_vulnerabilities&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;output_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cve_information&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Using Built-in Tools
&lt;/h4&gt;

&lt;p&gt;As I mentioned, you couldn’t use built-in and function tools inside one agent, so you need one more agent to use Google Search.&lt;/p&gt;

&lt;p&gt;Here is an excellent example of how powerful an agent is. It will Google a lot to find the necessary information and enrich your data with additional information.&lt;/p&gt;

&lt;p&gt;The same ideas apply: build some identity in instruction and perform some actions with built-in tools. The data is imported from the framework; you don’t need to create it manually or use it differently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;google_search&lt;/span&gt;

&lt;span class="n"&gt;google_search_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proof_of_concept_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GEMINI_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
            You are a cybersecurity expert specializing in supply chain vulnerabilities.
            Also you are a Google Search expert.

            You should find two types information in Google Search about given vulnerability:

            1. You should find PoC and exploits with google_search tool for given vulnerability and return related links to it.
            1.1. Usually information about PoC or Exploits could be found in vulnerability information (use &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cve_information&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; from previous agent)
            1.2. If provided links are not enough, you should use google_search tool again to find more information.

            2. You should find news about given vulnerability, collect dates of articles and return summary of them.

            Use the vulnerability name provided as an input. Vulnerability name usually starts with CVE- prefix.

            Store information in the session state under the keys &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;poc_links&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; and &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;news_summary&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;    
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;output_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google_search_summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;google_search&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also made one more Google agent to compensate for the information. Imagine if your tools couldn’t get information from API due to restrictions, not founds, rate limiting, or falling meteorites—you wouldn’t have information for the next processing. So, I made a compensation Agent that will Google a vulnerability to find it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;google_last_hope_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google_last_hope_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GEMINI_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    You are a cybersecurity expert specializing in supply chain vulnerabilities.
    Also you are a Google Search expert.

    WARNING: Skip instructions below if you already have information about vulnerability in session state by key: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cve_information&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,
     and return info to user information is enough.

    If information about given vulnerability is not available in state by key: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cve_information&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.
    You should use google_search tool to find information about given vulnerability.
    Use the vulnerability name provided as an input. Vulnerability name usually starts with CVE- prefix.

    Store information in the session state under the key &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cve_information&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Glueing Together
&lt;/h4&gt;

&lt;p&gt;Last but not least, part of our guide combines information and provides targeted output. What is the next step? Creating one more agent!&lt;/p&gt;

&lt;p&gt;Our agent will collect information from the state for the keys given and summarize it in the following format: You can read the details in the instruction section.&lt;/p&gt;

&lt;p&gt;I was hiding something secret from you: there will be different agents. Let me introduce Sequential Agent, Which will run each agent in your desired order! Just enumerate your agents in sub_agents, and that’s all.&lt;/p&gt;

&lt;p&gt;Our sequential agent will be the last agent in this guide, one to orchestrate them all. We will run each agent, collect information, and, at the end, summarize it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;vulnerability_summarizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vulnerability_summarizer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GEMINI_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    You&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;re a cybersecurity expert specializing in supply chain vulnerabilities.
    For vulnerability prioritization you should use the following rules:
    - Use information from session state under key &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cve_information&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;
    - Use information from session state under key &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;poc_links&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;
    - Use information from session state under key &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;news_summary&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;

    Provide information about vulnerability:
    - Title
    - Description (up to 250 characters)
    - Severity (if not available, try to to figure it out dependence on overall information)
    - Is it known to be exploited (KEV)? What required actions?
    - Does it have PoC or known exploits? Provide links to them.
    - What priority should be given to this vulnerability? Priority should be one of the following: High, Medium, Low
    -- If PoC or exploits are available - it should be High priority.
    - How often speaks about this vulnerability in news?
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;root_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SequentialAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vulnerability_prioritization_pipeline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sub_agents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cve_information_provider_agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;google_last_hope_agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;google_search_agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vulnerability_summarizer&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Run, Run, RUN!
&lt;/h4&gt;

&lt;p&gt;Everything is done. Let’s run with &lt;code&gt;adk web&lt;/code&gt; it and see what happens. You will get a web interface for experimenting and debugging.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk2qhcwpynnlh14p2y957.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk2qhcwpynnlh14p2y957.webp" alt="ADK running in terminal" width="800" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5cmv8l84326ao6c2cea.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5cmv8l84326ao6c2cea.webp" alt="AI agents at work" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just ask in the chat for information about the vulnerability, which will trigger the whole Agent chain. The last message will be our targeted output.&lt;/p&gt;

&lt;p&gt;Web UI also provides a good interface for debugging and watching what is inside your logic. Try to inspect all the buttons to get familiar with the UI.&lt;/p&gt;

&lt;h4&gt;
  
  
  At the end
&lt;/h4&gt;

&lt;p&gt;AI agents are powerful but have limitations, and as technology advances, it becomes even more powerful. This guide will be your entry point into the world of AI agents.&lt;/p&gt;

&lt;p&gt;Source code of agents you could find in &lt;a href="https://gist.github.com/NordCoderd/266a0fde5be1cc577cd0189c07b3dea3" rel="noopener noreferrer"&gt;Github Gist&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Are you enjoying this guide and want to build your own AI agent? Just comment on what you want to implement with this technology. I will choose the idea I like and implement it by writing a story. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Follow for the next stories!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>python</category>
      <category>ai</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>Malicious Packages in NPM and PyPI: How Typosquatting Threatens Developers</title>
      <dc:creator>Dmitry Protsenko</dc:creator>
      <pubDate>Mon, 21 Apr 2025 15:03:48 +0000</pubDate>
      <link>https://dev.to/protsenko/malicious-packages-in-npm-and-pypi-how-typosquatting-threatens-developers-134n</link>
      <guid>https://dev.to/protsenko/malicious-packages-in-npm-and-pypi-how-typosquatting-threatens-developers-134n</guid>
      <description>&lt;p&gt;Malicious packages lurk in NPM and PyPI — especially in NPM. If you’ve built front-end apps, you’ve likely used npm, pnpm, or yarn. You’ve probably tweaked &lt;code&gt;package.json&lt;/code&gt; or run &lt;code&gt;npm add something&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These tools streamline dependency management. Each install pulls code from npmjs.com and runs scripts locally — sometimes with deep system access.&lt;/p&gt;

&lt;p&gt;Uploading to these registries requires little effort. Hackers exploit this by releasing deceptive packages with typo-based names or minor variations. One wrong keystroke installs malware disguised as a trusted library. This tactic, known as &lt;em&gt;typosquatting&lt;/em&gt;, compromises machines and hijacks developer trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples of Recent Attacks
&lt;/h2&gt;

&lt;p&gt;Hackers still exploit typosquatting by uploading malicious packages to public repositories. They target both widely used libraries and those with loyal user bases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Malicious packages&lt;/strong&gt; &lt;code&gt;eslint&lt;/code&gt; and &lt;code&gt;@types/node&lt;/code&gt; (November 2024): In November 2024, hackers uploaded a malicious npm package named &lt;code&gt;@typescript_eslinter/eslint&lt;/code&gt; using a fake scope to mimic the legitimate &lt;code&gt;@typescript-eslint&lt;/code&gt; package. The malicious package became so popular that it gained &lt;a href="https://npm-stat.com/charts.html?package=@typescript_eslinter/eslint" rel="noopener noreferrer"&gt;hundreds of downloads daily&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Another example is &lt;code&gt;@types-node&lt;/code&gt;, which also turned out to be a fake package. It attracted nearly a &lt;a href="https://npm-stat.com/charts.html?package=types-node" rel="noopener noreferrer"&gt;thousand downloads per day&lt;/a&gt;. Read more in the &lt;a href="https://thehackernews.com/2024/12/thousands-download-malicious-npm.html" rel="noopener noreferrer"&gt;source article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhvfg779e2ob45t9kj56d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhvfg779e2ob45t9kj56d.png" alt="Statistics for downloading squatted package" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Targeted attack to steal Solana&lt;/strong&gt;: Hackers also targeted the Solana SDK by deploying malicious packages with names like &lt;code&gt;solana-transaction-toolkit&lt;/code&gt; and &lt;code&gt;solana-stable-web-huks&lt;/code&gt;. These malicious packages were explicitly designed to steal Solana from wallets. Read more in the &lt;a href="https://thehackernews.com/2025/01/hackers-deploy-malicious-npm-packages.html" rel="noopener noreferrer"&gt;source article&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  How Attackers Disguise Malicious Packages
&lt;/h2&gt;

&lt;p&gt;Typosquatting in package managers mirrors domain name scams — attackers count on subtle typos developers often miss.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Name Mimicry&lt;/strong&gt;: Attackers impersonate popular libraries by tweaking package names. They swap letters with nearby keys, omit characters, switch adjacent letters, duplicate them, or replace symbols — like dots with underscores or hyphens with lookalikes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Abusing Installation Scripts&lt;/strong&gt;: NPM lets packages define &lt;a href="https://docs.npmjs.com/cli/v9/using-npm/scripts" rel="noopener noreferrer"&gt;lifecycle scripts&lt;/a&gt; — like &lt;code&gt;preinstall&lt;/code&gt; or &lt;code&gt;postinstall&lt;/code&gt;—that runs automatically during installation. This opens a common path for malware. A malicious package can execute code the moment someone runs &lt;code&gt;npm install&lt;/code&gt;, without any extra action from the developer. Attackers regularly &lt;a href="https://blog.sandworm.dev/dissecting-npm-malware-five-packages-and-their-evil-install-scripts" rel="noopener noreferrer"&gt;exploit this&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These scripts can steal private data, open reverse shells, or drop obfuscated code with full system access. Fortunately, you can skip them by running &lt;code&gt;npm install --ignore-scripts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwm2a0r4xqv3kigkn44b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwm2a0r4xqv3kigkn44b.png" alt="Example of post-install script" width="800" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example of the malicious script in postinstall&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Combosquatting / Brandjacking&lt;/strong&gt;: Hackers sometimes append words to trusted package names to hijack brand reputation. In one campaign targeting Roblox developers, they published packages like &lt;code&gt;noblox.js-async&lt;/code&gt; and &lt;code&gt;noblox.js-proxy-server&lt;/code&gt;—variants meant to mimic the legitimate &lt;code&gt;noblox.js&lt;/code&gt; library.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Starjacking&lt;/strong&gt;: Some attackers link malicious packages to popular GitHub repos. Since registries don’t verify these links, a sketchy package can point to a legit, star-heavy repo — tricking users into trusting it at a glance.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, I found a &lt;a href="https://www.npmjs.com/package/eslint-config-geact-app?activeTab=code" rel="noopener noreferrer"&gt;typo-squatted&lt;/a&gt; package linked to the original repo—one with over 100,000 stars—making it look legitimate at first glance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcpkyubenqmvmc6ygvsmn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcpkyubenqmvmc6ygvsmn.png" alt="eslint-config-geact-app" width="800" height="649"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbjt2hojmcyrh47p70roa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbjt2hojmcyrh47p70roa.png" alt="Original package page" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Copying Metadata&lt;/strong&gt;: To pass as legitimate, attackers clone metadata — README, versions, descriptions, and keywords. Some even reuse original source code, injecting obfuscated malware to avoid detection. In the TypeScript ESLint typosquat, a package by &lt;code&gt;typescript_eslinter&lt;/code&gt; mimicked &lt;code&gt;typescript-eslint&lt;/code&gt;, linking to a fake GitHub repo that mirrored the real project’s structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These techniques prey on trust and human error. They exploit the belief that official registries are safe and take advantage of how seamlessly tools like NPM install packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Research
&lt;/h2&gt;

&lt;p&gt;I ran a small experiment to see how deep the rabbit hole goes—how many typo-squatted names I could generate and how many remained available for publishing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Methodology&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Simple process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Collected popular NPM package names&lt;/li&gt;
&lt;li&gt;Generated mimicry names using known deception techniques&lt;/li&gt;
&lt;li&gt;Checked availability for publishing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trending package names are easy to find — many curated lists track them. I used &lt;a href="https://gist.github.com/anvaka/8e8fa57c7ee1350e3491" rel="noopener noreferrer"&gt;one&lt;/a&gt; dataset and wrote a Python script to generate lookalike names. From nearly 2,000 original packages, I created 227,389 variations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mutation Techniques
&lt;/h2&gt;

&lt;p&gt;To generate typo-squatted variations, I applied the following mutations to each package name:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Letter swap&lt;/strong&gt;: Replaced one letter with its QWERTY keyboard neighbor or with a nearby letter in the name&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Letter removal&lt;/strong&gt;: Removed one letter at a time, skipping the scope symbol (&lt;code&gt;@&lt;/code&gt;) and dashes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Letter duplication&lt;/strong&gt;: Duplicated one letter at a time across the name&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symbol replacement&lt;/strong&gt;: Swapped symbols — like changing hyphens (&lt;code&gt;-&lt;/code&gt;) to underscores (&lt;code&gt;_&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Letter insertion&lt;/strong&gt;: Inserted a keyboard-neighbor letter after each character in sequence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I found a &lt;a href="https://github.com/nice-registry/all-the-package-names" rel="noopener noreferrer"&gt;GitHub project&lt;/a&gt; that indexes all existing NPM package names. With it, I filtered mutated names against real ones to identify available packages. I revalidated missing names using the &lt;a href="https://www.npmjs.com" rel="noopener noreferrer"&gt;npmjs.com&lt;/a&gt; API to double-check.&lt;/p&gt;

&lt;p&gt;In total, I generated over &lt;strong&gt;222,000&lt;/strong&gt; package name variations available for publishing. About &lt;strong&gt;4,500&lt;/strong&gt; were already taken, and a small number failed due to validation errors or malformed mutations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiov8qbfve47jjkacodk2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiov8qbfve47jjkacodk2.png" alt="Statistics for packages during research" width="800" height="724"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My research uncovered thousands of typosquatting packages—some already published, others &lt;a href="https://www.npmjs.com/package/eslint-config-geact-app?activeTab=code" rel="noopener noreferrer"&gt;still available&lt;/a&gt;. Many squatted packages contained malicious code and were removed from NPM. Yet others, typo-ridden but seemingly harmless, remain live and get downloads weekly.&lt;/p&gt;

&lt;p&gt;They often link to official repositories and appear legitimate — until you notice the subtle typo. Even if a package looks clean now, that doesn’t guarantee it will stay that way.&lt;/p&gt;

&lt;p&gt;Typosquatting remains a serious attack vector, especially for popular packages, but the situation is not as bad as it seems. Registries like &lt;a href="https://www.npmjs.com" rel="noopener noreferrer"&gt;npmjs.com&lt;/a&gt; now block names that closely resemble existing ones, such as those differing by only one letter.&lt;/p&gt;

&lt;p&gt;Cybersecurity firms also monitor new packages and updates to catch threats early. Still, some slip through. A package might be installed before it’s flagged and removed.&lt;/p&gt;

&lt;p&gt;Even a single infection can prove dangerous. Developers can access private codebases, credentials, and infrastructure, making them prime targets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protecting Against Those Attacks
&lt;/h2&gt;

&lt;p&gt;Some tools scan build files for known malicious dependencies but only flag what has already been identified. Unknown threats still slip through.&lt;/p&gt;

&lt;p&gt;Antivirus software can help post-installation by catching crypto-lockers, stealers, and other malware types. Still, technical tools alone can’t guarantee safety.&lt;/p&gt;

&lt;p&gt;Ultimately, protecting yourself requires vigilance. Here are a few practical tips to reduce the risk of installing malicious packages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Double-check package names:&lt;/strong&gt; Typosquatting only works if you mistype. Always verify the exact name before installing a package.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Inspect before installing:&lt;/strong&gt; Before adding a dependency — especially an obscure one — check its NPM page. Look at the README, version history, and publisher profile. Red flags: vague descriptions, recent creation, or a publisher with no other packages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Use trusted sources:&lt;/strong&gt; Follow links from official websites or GitHub repositories instead of relying solely on NPM search results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Lockdown versions:&lt;/strong&gt; Use &lt;code&gt;package-lock.json&lt;/code&gt; or &lt;code&gt;yarn.lock&lt;/code&gt; to freeze dependency versions. Lock files help ensure consistency and avoid unexpected package changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Watch for unusual behavior:&lt;/strong&gt; Pay attention to post-install messages or prompts. Legitimate libraries shouldn’t contact remote servers or request elevated permissions during setup.&lt;/p&gt;

&lt;p&gt;Malicious packages are a threat that could happen to anybody, and I hope this story helps you learn more about this problem and how to avoid it.&lt;/p&gt;

&lt;p&gt;If you’re into cybersecurity or software development, follow me — I publish new articles twice weekly.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>npm</category>
      <category>python</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>You Don’t Need a Pet Project to Be a Good Dev: How I Beat FOMO</title>
      <dc:creator>Dmitry Protsenko</dc:creator>
      <pubDate>Wed, 16 Apr 2025 15:14:22 +0000</pubDate>
      <link>https://dev.to/protsenko/you-dont-need-a-pet-project-to-be-a-good-dev-how-i-beat-fomo-3nho</link>
      <guid>https://dev.to/protsenko/you-dont-need-a-pet-project-to-be-a-good-dev-how-i-beat-fomo-3nho</guid>
      <description>&lt;p&gt;Do you ever feel like you’re falling behind in your tech career? Everyone builds pet projects, speaks at conferences, and posts on LinkedIn. Meanwhile, you’re just trying to finish your sprint without burning out. That’s &lt;strong&gt;FOMO&lt;/strong&gt; — and here’s how I’ve started to deal with it. &lt;/p&gt;

&lt;h2&gt;
  
  
  What is FOMO, and how could CBT help
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;FOMO&lt;/strong&gt;, or the fear of missing out, is the anxiety that one might be excluded from rewarding experiences others enjoy, a feeling often intensified by social media. &lt;/p&gt;

&lt;p&gt;Here’s how I’ve been dealing with FOMO using practical techniques based on Cognitive Behavioral Therapy (CBT). Here’s a simplified way I think about &lt;strong&gt;CBT&lt;/strong&gt; (from a dev’s perspective):&lt;/p&gt;

&lt;p&gt;CBT helps you recognize patterns in how you think, feel, and behave — and teaches you how to test and restructure those patterns, like debugging buggy logic and refactoring. Distorted thoughts appear from common thinking traps known as cognitive distortions, which can cause unnecessary emotional distress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of cognitive distortions
&lt;/h2&gt;

&lt;p&gt;Knowing cognitive distortions is fundamental for self-awareness. CBT teaches that it’s not the situation that causes distress but how we interpret it. We can change how we feel and act by identifying distortions in our automatic thoughts — often fast, inaccurate, and fear-based.&lt;/p&gt;

&lt;p&gt;Here are examples of common cognitive distortions and how they might appear in a developer’s life:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;All-or-nothing Thinking (Black-and-White Thinking):&lt;/strong&gt; You see things in extremes — complete success or failure. &lt;em&gt;I don’t have a pet project — I’m a bad developer.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overgeneralization:&lt;/strong&gt; Taking one negative event and seeing it as a never-ending pattern. &lt;em&gt;I failed this interview — I will never get a good job.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mental Filter:&lt;/strong&gt; Focusing only on the negative part of a situation and ignoring the positive. &lt;em&gt;The team lead left many commentaries in my PR. I must have done a worse job (ignoring all the praise).&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disqualifying the Positive:&lt;/strong&gt; Rejecting positive experiences by insisting they don’t count. &lt;em&gt;I completed these projects, but anyone could’ve done it.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jumping to Conclusions:&lt;/strong&gt; Assuming things without evidence. It has two forms: &lt;strong&gt;Mind Reading:&lt;/strong&gt; &lt;em&gt;They probably think I’m not smart,&lt;/em&gt; and &lt;strong&gt;Fortune Telling:&lt;/strong&gt; &lt;em&gt;the team lead will fire me because I made bugs in the project.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catastrophizing (Magnification) / Minimization:&lt;/strong&gt; Expecting the worst or downplaying the good. &lt;em&gt;Making one mistake in this talk will ruin my career.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emotional Reasoning:&lt;/strong&gt; Believing that feelings reflect reality. &lt;em&gt;I feel overwhelmed, so I must be incapable.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Should Statements:&lt;/strong&gt; Using “should,” “must,” or “ought to” to pressure yourself or others. &lt;em&gt;I should contribute to open-source projects and make my own.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Labeling and Mislabeling:&lt;/strong&gt; Attaching harsh labels to yourself or others. &lt;em&gt;I’m an incompetent developer.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personalization:&lt;/strong&gt; Blaming yourself for things outside your control — or taking things too personally. _The project failed — it’s all my fault. _&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Learning your automatic thoughts and reframing them will reduce the anxiety and compulsive behaviors that come with FOMO. A reframed thought should be realistic, not overly positive — just grounded in facts, not fear. &lt;/p&gt;

&lt;p&gt;By practicing, you will build skills to control FOMO, and there is no need to eliminate all of its sources. &lt;/p&gt;

&lt;h2&gt;
  
  
  Track and Challenge Thoughts in Writing.
&lt;/h2&gt;

&lt;p&gt;Before challenging a thought, pause and acknowledge what you’re feeling. It’s okay to feel overwhelmed, anxious, or discouraged — your emotions are valid, even if the thoughts behind them aren’t entirely accurate.&lt;/p&gt;

&lt;p&gt;This is my favorite self-help tool — writing a diary in a structured way to analyze your thoughts. Thought records help you capture the cycle of &lt;strong&gt;Situation&lt;/strong&gt; → &lt;strong&gt;Thoughts&lt;/strong&gt; → &lt;strong&gt;Emotions&lt;/strong&gt; → &lt;strong&gt;Alternative Views&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Whenever you notice anxiety, negative feelings, or thoughts like: I’m behind. Make a break and write a journal using the following structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Situation&lt;/strong&gt; — What triggered the FOMO feeling? Be specific about the event or context. (e.g., “&lt;em&gt;I scrolled Medium and saw many articles about benefits of contributing to pet projects&lt;/em&gt;”)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Thought(s)&lt;/strong&gt; — What thought flashed through your mind, and what did it mean to you? (e.g., “&lt;em&gt;I don’t contribute to open-source projects, I’m falling behind in the career race.&lt;/em&gt;”)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emotions&lt;/strong&gt; — What did you feel, and how intense was it (0–10)? (e.g., Anxiety — 7/10)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cognitive Distortions&lt;/strong&gt; — Are you catastrophizing, mind-reading, all-or-nothing? Labeling it can remind you that the thought isn’t fully reliable (e.g., &lt;em&gt;Identified catastrophizing (assuming not contributing to open-source is breaking your career) and all-or-nothing thinking&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence For &amp;amp; Against&lt;/strong&gt; — Now act like a detective or a scientist examining the thought. List facts supporting the thought and facts against it:&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Supporting evidence:&lt;/em&gt; “Only one colleague contributed to the open-source project. He is working as a staff engineer.”&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Contradicting evidence:&lt;/em&gt; “I do the best at my current position, provide value to the business, and complete projects up to deadlines. I’ve been promoted every year and received solid bonuses.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alternative (Balanced) Thought&lt;/strong&gt; — Write a more rational conclusion based on the evidence. This is your reframed thought to replace the original. You should acknowledge the situation without fear. &lt;em&gt;Only one colleague contributes, and hundreds don’t. He contributes to open-source because he maintains our open-source framework. He was promoted to staff engineer even before he started contributing. My manager even told me that open-source contributions aren’t the only path to promotion.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outcome&lt;/strong&gt; — Notice how your emotions or perspective shift after reframing. Did your anxiety lessen (rate it again)? Do you feel more confident or calm? (e.g., &lt;em&gt;Anxiety dropped to 3/10; I feel more in control knowing I have a plan to learn what I need.&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Journaling has helped me put anxiety into perspective. When I write down my thoughts, explore the evidence, and reframe them, I shift from reacting emotionally to thinking clearly — like a developer approaching a tough bug with logic and patience.&lt;/p&gt;

&lt;p&gt;I’ve used this method not only for FOMO but also for anxiety before speaking, writing blogs, and working on projects. It’s not magic — it’s just structured thinking. But it works.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While this approach can be powerful, it’s not a cure-all. If anxiety, self-doubt, or stress becomes overwhelming or interferes with daily life, it’s worth talking to a therapist. CBT works even better with support — you don’t have to figure it out alone.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  At the end
&lt;/h2&gt;

&lt;p&gt;In this industry, it’s easy to believe that only skills matter. But mindset matters, too. I’ve seen smart developers burn out or miss chances because they were too scared, self-critical, or caught in comparison loops to act. &lt;/p&gt;

&lt;p&gt;So here’s my advice: build your mental tools like you build your tech stack. Take care of your mind. You don’t need to do everything — take the next step. &lt;/p&gt;

&lt;p&gt;If this helps you, follow me and leave comments. I’d love to hear your thoughts or experiences with FOMO and mindset. Let’s help each other grow.&lt;/p&gt;

</description>
      <category>mentalhealth</category>
      <category>softwaredevelopment</category>
      <category>career</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>AI Will Take My Job, But Not Today</title>
      <dc:creator>Dmitry Protsenko</dc:creator>
      <pubDate>Mon, 14 Apr 2025 16:34:31 +0000</pubDate>
      <link>https://dev.to/protsenko/ai-will-take-my-job-but-not-today-3nae</link>
      <guid>https://dev.to/protsenko/ai-will-take-my-job-but-not-today-3nae</guid>
      <description>&lt;p&gt;Recently, I noticed new feelings and thoughts about AI in Software Development. I felt upset about recent news such as “Programming no longer needed in the nearest time” and “AI will replace programmers”. I took a lot of time to develop a career as a software developer, and now AI will replace me. &lt;/p&gt;

&lt;p&gt;We live in a fantastic world where AI can solve many technical problems, and this technology makes our lives better. I use it almost every day, but not for production-ready programming. Let’s speak in the facts of why AI wouldn’t replace me in the closest time. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results consistency:&lt;/strong&gt; AI could provide you with working code day after day, without pauses and hunger, but it does not consistently provide good results. The generated code could be laconic, chaotic, and long, with different naming and logic structures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintainability of results:&lt;/strong&gt; AI could provide code that solves your problem but generates code that is hard to maintain. Software Development has not ended with programming. Developers also maintain code by improving performance, fixing bugs, creating new features, etc. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Too long code:&lt;/strong&gt; Poor maintainability is the side of the problem. AI generates a lot of code with many comments in one file. New functions are added to the end of a file or somewhere. In common cases, when the developer works on functions, they could adopt them to reuse existing methods or extend them, accurately structure files, classes, and methods to have well-organized projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hallucinations:&lt;/strong&gt; This is not a solved problem for LLM models like ChatGPT. AI could invent something that programming languages and libraries don’t have. During my workflow or requirements, AI often suggests methods in libraries that didn’t exist before. If you point that problem to the Assistant — it invents new, not existing methods, and very apologies!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knowledge limitations:&lt;/strong&gt; AI generates code for the most common scenarios. AI doesn’t know much about new versions of libraries or languages. It could generate only what he learned for most common cases. One good example is Exposed (ORM framework). In new versions of that framework, they changed how data is retrieved. They changed API. Old methods were deprecated for some time, and old methods were removed after some period. So, AI now generates invalid code that doesn’t work. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Business domain and company knowledge:&lt;/strong&gt; AI should know how to solve problems specific to your company. Problems are not solved only by AI: collaboration, research, and expertise should happen to make code that solves problems. AI couldn’t invent ways to help you solve problems effectively, probably without code. It could provide you with an average solution for problems that could be resolved in different, easier ways. &lt;/p&gt;

&lt;h2&gt;
  
  
  At the end
&lt;/h2&gt;

&lt;p&gt;AI has limitations. Some problems could be solved with prompt engineering, but others could not; different approaches could be made, like AI agents. I believe in the AI future. It will take my job in its current state. Developer work will be changed insanely from how It was decades ago when modern high-level programming languages were created. The same will be true with the adoption of more powerful AI tools. &lt;/p&gt;

&lt;p&gt;Do you want to know what matters in a Developer’s work? Read my past stories, and follow me to read upcoming ones.&lt;/p&gt;

</description>
      <category>career</category>
      <category>softwaredevelopment</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
