Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
## General rules
1. Add only new files to git. Do not modify the existing staging unless explicitly required.

## Verification development stages

1. Implement everything in pure Kotlin, making full use of IDE-assisted language features and inspections.
2. Add inspection messages and tooltips to [SecurityPluginBundle.properties](src/main/resources/messages/SecurityPluginBundle.properties).
3. Create the inspection draft in the technology-specific folder under [securityLinter](src/main/kotlin/dev/protsenko/securityLinter).
4. Add tests covering the inspection:
- Example of a Docker inspection: [DS003SshPortExposed.kt](src/test/kotlin/dev/protsenko/securityLinter/docker/DS003SshPortExposed.kt)
- Example of test data for Docker inspections: [DS003](src/test/testData/docker/DS003)
- Example of extended test data with additional cases: [DS029](src/test/testData/docker/DS029)
5. Create an inspection description in HTML using the same filename as the inspection class in [inspectionDescriptions](src/main/resources/inspectionDescriptions).
6. Register the inspection in XML:
- For YAML-based inspections: [dev.protsenko.security-linter-yaml.xml](src/main/resources/META-INF/dev.protsenko.security-linter-yaml.xml)
- For general and Docker-based inspections: [plugin.xml](src/main/resources/META-INF/plugin.xml)
7. If the inspection validates Docker RUN commands, follow the rules defined in [RunCommandValidator.kt](src/main/kotlin/dev/protsenko/securityLinter/docker/checker/core/RunCommandValidator.kt).
- Register the required extension points in [plugin.xml](src/main/resources/META-INF/plugin.xml).
8. If the inspection is YAML-based, use the bundled helper classes such as [YamlPath.kt](src/main/kotlin/dev/protsenko/securityLinter/utils/YamlPath.kt).
9. Ensure that all tests pass.
10. If the plugin version has not been updated in [gradle.properties](gradle.properties), update it.
11. Update the changelog in [CHANGELOG.md](CHANGELOG.md).
12. Perform a full build and run the complete test suite for the plugin.

## Summary for verification development

1. All inspections must be registered and covered by tests.
2. All inspections must leverage Kotlin and IDE capabilities effectively.
3. All reusable helper utilities should be covered by JUnit 3 tests.
4. All inspections must cover corner cases and include extended test scenarios.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

# Cloud (IaC) Security Changelog

## [2.0.17] 15-03-2026

### Added

- Inspection: [wget without recommended flags](https://protsenko.dev/infrastructure-security/avoid-wget-without-progress-or-quiet/)

## [2.0.16] 07-02-2026

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pluginGroup = dev.protsenko.securityLinter
pluginName = Cloud (IaC) Security
pluginRepositoryUrl = https://github.com/NordCoderd/cloud-security-plugin
pluginVersion = 2.0.16
pluginVersion = 2.0.17

# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 231
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package dev.protsenko.securityLinter.docker.checker

import dev.protsenko.securityLinter.docker.checker.core.RunCommandValidator

object WgetProgressBarValidator : RunCommandValidator {
private val wgetPattern = Regex("""\bwget\b""", RegexOption.IGNORE_CASE)
private val separators = Regex("""\s*(?:&&|;|\n)\s*""")
private val recommendedWgetOptionsPattern =
Regex(
pattern = """(?:^|\s)(--progress=dot:giga|--quiet|--no-verbose|-q(?:\S*)|-nv(?:\S*))(?=\s|$)""",
options = setOf(RegexOption.IGNORE_CASE),
)

override fun isValid(command: String): Boolean {
if (!wgetPattern.containsMatchIn(command)) return true

val cmd = command.removePrefix("RUN").trim()
val individualCommands = separators.split(cmd)

for (individualCommand in individualCommands) {
val cmdTrimmed = individualCommand.trim()
if (!wgetPattern.containsMatchIn(cmdTrimmed)) continue
if (!recommendedWgetOptionsPattern.containsMatchIn(cmdTrimmed)) {
return false
}
}

return true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package dev.protsenko.securityLinter.docker.inspection.run.impl

import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElement
import dev.protsenko.securityLinter.core.HtmlProblemDescriptor
import dev.protsenko.securityLinter.core.SecurityPluginBundle
import dev.protsenko.securityLinter.docker.checker.WgetProgressBarValidator
import dev.protsenko.securityLinter.docker.inspection.run.core.DockerfileRunAnalyzer

class WgetWithoutRecommendedFlagsAnalyzer : DockerfileRunAnalyzer {
override fun handle(
runCommand: String,
psiElement: PsiElement,
holder: ProblemsHolder,
) {
if (!WgetProgressBarValidator.isValid(runCommand)) {
val descriptor =
HtmlProblemDescriptor(
psiElement,
SecurityPluginBundle.message("dfs032.documentation"),
SecurityPluginBundle.message("dfs032.problem-text"),
ProblemHighlightType.WARNING,
)

holder.registerProblem(descriptor)
}
}
}
3 changes: 2 additions & 1 deletion src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
<dockerFileRunAnalyzer implementation="dev.protsenko.securityLinter.docker.inspection.run.impl.UserAddAnalyzer"/>
<dockerFileRunAnalyzer implementation="dev.protsenko.securityLinter.docker.inspection.run.impl.ApkNoCacheValidatorAnalyzer"/>
<dockerFileRunAnalyzer implementation="dev.protsenko.securityLinter.docker.inspection.run.impl.PipNoCacheDirAnalyzer"/>
<dockerFileRunAnalyzer implementation="dev.protsenko.securityLinter.docker.inspection.run.impl.WgetWithoutRecommendedFlagsAnalyzer"/>

<dockerFileExposeAnalyzer implementation="dev.protsenko.securityLinter.docker.inspection.expose.impl.SshPortExposedAnalyzer"/>
<dockerFileExposeAnalyzer implementation="dev.protsenko.securityLinter.docker.inspection.expose.impl.ExposedPortOutOfRangeAnalyzer"/>
Expand All @@ -116,4 +117,4 @@
interface="dev.protsenko.securityLinter.docker.inspection.copyAndAdd.core.DockerfileCopyOrAddAnalyzer"/>
</extensionPoints>

</idea-plugin>
</idea-plugin>
5 changes: 4 additions & 1 deletion src/main/resources/messages/SecurityPluginBundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ dfs030.problem-text=Alpine package installation without --no-cache, consider usi
### dfs031
dfs031.documentation=using-pip-install-without-no-cache-dir
dfs031.problem-text=The pip package installations without --no-cache-dir, consider using it to reduce image size.
### dfs032
dfs032.documentation=avoid-wget-without-progress-or-quiet
dfs032.problem-text=Avoid using wget without a progress/quiet option. Use --progress=dot:giga or consider -q/-nv (--quiet/--no-verbose).

### dcs001 [only docker-compose]
ds033.using-privileged=Using privileged: true grants full root access to the host, bypassing isolation mechanisms.
Expand Down Expand Up @@ -178,4 +181,4 @@ kube012.problem-text=This volume type may expose the node or bypass pod isolatio
# Not implemented
ds029.missing-healthcheck=Missing HEALTHCHECK instruction
ds023.multiple-exposed-port=Port {0} exposed more than one time.
ds024.wrong-port-definition=Port {0} is incorrect.
ds024.wrong-port-definition=Port {0} is incorrect.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dev.protsenko.securityLinter.docker

import com.intellij.codeInspection.LocalInspectionTool
import dev.protsenko.securityLinter.core.DockerHighlightingBaseTest
import dev.protsenko.securityLinter.docker.inspection.run.DockerfileRunInspection

class DS036WgetWithoutProgressOrQuietInspectionTest(
override val ruleFolderName: String = "DS036",
override val customFiles: Set<String> = emptySet<String>(),
override val targetInspection: LocalInspectionTool = DockerfileRunInspection()
) : DockerHighlightingBaseTest()
2 changes: 1 addition & 1 deletion src/test/testData/docker/DS013/Dockerfile.denied
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM debian:stable-20210621
<warning descr="RUN using 'wget' and 'curl'
Avoid using both 'wget' and 'curl' since these tools have the same effect.">RUN wget http://bing.com</warning>
Avoid using both 'wget' and 'curl' since these tools have the same effect.">RUN wget -q http://bing.com</warning>

FROM baseimage:1.0
USER mike
Expand Down
6 changes: 6 additions & 0 deletions src/test/testData/docker/DS036/Dockerfile.allowed
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM ubuntu:24.04
RUN wget --progress=dot:giga https://example.com/archive.tar.gz && \
wget --quiet https://example.com/archive2.tar.gz && \
wget --no-verbose https://example.com/archive3.tar.gz && \
wget -qO /tmp/archive4.tar.gz https://example.com/archive4.tar.gz && \
wget -nv https://example.com/archive5.tar.gz
2 changes: 2 additions & 0 deletions src/test/testData/docker/DS036/Dockerfile.denied
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM ubuntu:24.04
<warning descr="Avoid using wget without a progress/quiet option. Use --progress=dot:giga or consider -q/-nv (--quiet/--no-verbose).">RUN wget https://example.com/archive.tar.gz</warning>
Loading