add CONTRIBUTING

This commit is contained in:
Zeecka
2026-01-03 01:44:56 +01:00
parent b4bdef611f
commit b3605047ad
3 changed files with 401 additions and 19 deletions
+186
View File
@@ -0,0 +1,186 @@
# Contributing to Aperi'Solve
Thank you for your interest in contributing! We welcome all contributions, whether it's bug fixes, new features, or documentation improvements.
## Getting Started
> [!TIP]
> New to the project? Adding a new analyzer is a great first contribution! Check out our [Adding a New Analyzer Guide](docs/adding-analyzer.md).
### Quick Steps
1. **Fork the repository**
2. **Create a feature branch** (`git checkout -b feature/amazing-feature`)
3. **Make your changes**
4. **Test your changes**
5. **Commit with clear messages** (`git commit -m 'Add amazing feature'`)
6. **Push to your fork** (`git push origin feature/amazing-feature`)
7. **Open a Pull Request** describing your changes
## What Can You Contribute?
### 🔧 Adding New Analyzers
Want to add support for a new steganography or forensics tool? We'd love that!
> [!NOTE]
> Check out our detailed tutorial: [Adding a New Analyzer](docs/adding-analyzer.md)
**Quick checklist for new analyzers:**
- [ ] Create analyzer file in `analyzers/`
- [ ] Extend `SubprocessAnalyzer` base class
- [ ] Register in `workers.py`
- [ ] Add tool to Docker setup if needed
- [ ] Test with sample images
- [ ] Update documentation
### 🐛 Bug Fixes
Found a bug? Please:
- Check if it's already reported in [Issues](../../issues)
- If not, open a new issue with reproduction steps
- Feel free to submit a PR with the fix!
### 📚 Documentation
Help us improve:
- Fix typos or unclear explanations
- Add examples and use cases
- Improve installation instructions
- Translate documentation
### 🎨 UI/UX Improvements
Contributions to the web interface are welcome:
- Better error messages
- Improved result presentation
- Mobile responsiveness
- Accessibility improvements
## Code Style & Quality
> [!IMPORTANT]
> Please follow the code style and run linters before submitting.
The project adheres to:
- **Black** (line length 100)
- **Isort** (profile black)
- **Flake8** (ignoring E203, E501, W503)
- **Pylint** (ignoring W0718, R0903, R0801)
- **Mypy** (ignoring unused-awaitable)
See [.pre-commit-config.yaml](.pre-commit-config.yaml) for exact configuration.
### Running Linters Locally
> [!TIP]
> Install pre-commit hooks to automatically check your code before commits:
> ```bash
> pip install pre-commit
> pre-commit install
> ```
**Manual checks:**
```bash
# Format code
black . --line-length 100
isort . --profile black
# Check for issues
flake8 .
pylint **/*.py
mypy .
```
> [!NOTE]
> CI will automatically run these checks on every PR. Make sure they pass!
## Pull Request Guidelines
Use clear, descriptive titles:
- ✅ `Add stegdetect analyzer`
- ✅ `Fix binwalk extraction error handling`
- ✅ `Update documentation for password-protected tools`
- ❌ `Update code`
- ❌ `Fix bug`
### Commit Messages
Write clear, concise commit messages:
```bash
# Good
git commit -m "Add zsteg analyzer for PNG steganography detection"
git commit -m "Fix foremost error detection when no files extracted"
# Not so good
git commit -m "updates"
git commit -m "fix"
```
## Adding New Dependencies
> [!CAUTION]
> Adding new dependencies requires careful consideration.
If your contribution needs new dependencies:
1. **Python packages**: Add to `requirements.txt`
2. **System tools**: Add to `Dockerfile`
3. **Explain why** in your PR description
4. **Keep dependencies minimal** - avoid adding large libraries for small features
## Docker and Tool Installation
When adding a new analyzer that requires a new tool:
```dockerfile
# In Dockerfile
RUN apt-get update && apt-get install -y \
your-new-tool \
&& rm -rf /var/lib/apt/lists/*
```
Or for tools requiring compilation:
```dockerfile
RUN git clone https://github.com/tool/repo.git /tmp/tool && \
cd /tmp/tool && \
make && make install && \
rm -rf /tmp/tool
```
> [!NOTE]
> Test the Docker build locally before submitting!
## Code Review Process
1. **Automated checks** run first (linters, tests)
2. **Maintainer review** - we'll provide feedback
3. **Address feedback** - update your PR as needed
4. **Approval & merge** - once everything looks good!
> [!TIP]
> Don't worry if you need to make changes - it's a normal part of the process!
## Need Help?
- 💬 **Questions?** Open a [Discussion](../../discussions)
- 🐛 **Found a bug?** Open an [Issue](../../issues)
- 📧 **Email:** [Your contact email]
- 💡 **Ideas?** We'd love to hear them in Discussions!
## Recognition
> [!NOTE]
> All contributors will be recognized in our [Contributors](../../graphs/contributors) page and in release notes!
## License
By contributing, you agree that your contributions will be licensed under the same license as the project (see [LICENSE](LICENSE)).
---
**Thank you for contributing! 🎉**
Your efforts help make this tool better for the entire security and CTF community!
+4 -19
View File
@@ -191,32 +191,17 @@ This separation keeps heavy tools (binwalk, foremost, zsteg, etc.) isolated and
## Roadmap
- [ ] **[Bug]** Duplicate key value violates unique constraint "submission_pkey"
- [ ] **[Enhancement]** Server continuous deployment
- [ ] **[Feature]** Zsteg: full extraction (--all) and download of discovered files (mp3, etc.)
- [ ] **[Feature]** Mobile-friendly UI / test on mobile
- [ ] **[Feature]** i18n (internationalization)
- [ ] **[Feature]** Rootless / unprivileged analyzers
- [ ] **[Feature]** Improve analyzer sandboxing (e.g., per-analyzer containers)
See [Issues](../../issues) for more!
## Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Open a pull request describing your change
> [!IMPORTANT]
> Please follow the code style and run linters before submitting. The project adheres to:
>
> - Black (line length 100)
> - Isort (profile black)
> - Flake8 (ignoring E203, E501, W503)
> - Pylint (ignoring W0718, R0903, R0801)
> - Mypy (ignoring unused-awaitable)
>
> See [.pre-commit-config.yaml](.pre-commit-config.yaml) file.
CI will run these checks on each PR.
See [CONTRIBUTING.md](CONTRIBUTING.md).
## Security
+211
View File
@@ -0,0 +1,211 @@
# Adding a New Analyzer
This guide shows you how to add a new image analysis tool to the system.
## Quick Start
1. Create a new file in `analyzers/` from template file `template_analyzer.py`.
2. Register it in `workers.py`
## Basic Template
```python
from pathlib import Path
from typing import Optional
from .base_analyzer import SubprocessAnalyzer
class MyToolAnalyzer(SubprocessAnalyzer):
def __init__(self, input_img: Path, output_dir: Path) -> None:
super().__init__("mytool", input_img, output_dir)
self.cmd = ["mytool", self.img]
def analyze_mytool(input_img: Path, output_dir: Path) -> None:
analyzer = MyToolAnalyzer(input_img, output_dir)
analyzer.analyze()
```
You can also see `template_analyzer.py`.
## Common Scenarios
### Simple Text Output Tool
Like `strings` or `exiftool` - just reads the file and outputs text:
```python
class StringsAnalyzer(SubprocessAnalyzer):
def __init__(self, input_img: Path, output_dir: Path) -> None:
super().__init__("strings", input_img, output_dir)
self.cmd = ["strings", self.img]
```
### Tool That Extracts Files
Like `binwalk` or `foremost` - extracts files that get zipped for download:
```python
class ForemostAnalyzer(SubprocessAnalyzer):
def __init__(self, input_img: Path, output_dir: Path) -> None:
super().__init__("foremost", input_img, output_dir, has_archive=True)
self.cmd = ["foremost", "-o", str(self.get_extracted_dir()), "-i", self.img]
```
Note the `has_archive=True` parameter.
### Tool With Password Support
Like `steghide` or `openstego`:
```python
class OutguessAnalyzer(SubprocessAnalyzer):
def __init__(self, input_img: Path, output_dir: Path) -> None:
super().__init__("outguess", input_img, output_dir, has_archive=True)
def build_cmd(self, password: Optional[str] = None) -> list[str]:
out = str(self.get_extracted_dir() / "outguess.data")
if password:
return ["outguess", "-k", password, "-r", self.img, out]
return ["outguess", "-r", self.img, out]
def analyze_outguess(input_img: Path, output_dir: Path, password: Optional[str] = None) -> None:
analyzer = OutguessAnalyzer(input_img, output_dir)
if password:
analyzer.analyze(password)
else:
analyzer.analyze()
```
## Customizing Behavior
### Custom Error Detection
Override `is_error()` to define what counts as an error:
```python
def is_error(self, returncode: int, stdout: str, stderr: str, zip_exist: bool) -> bool:
# Only error if no files were extracted
return len(stderr) > 0 and not zip_exist
```
### Custom Output Formatting
Override `process_output()` to format the results:
```python
def process_output(self, stdout: str, stderr: str) -> dict[str, str]:
# Parse into key-value pairs
metadata = {}
for line in stdout.split("\n"):
if ":" in line:
key, value = line.split(":", 1)
metadata[key.strip()] = value.strip()
return metadata
```
### User-Friendly Error Messages
Override `process_error()` to provide helpful messages:
```python
def process_error(self, stdout: str, stderr: str) -> str:
if "not supported" in stdout:
return "This file format is not supported (PNG only)"
return stderr
```
## Registering Your Analyzer
Add it to `workers.py` in the `analyze_image` function:
```python
# Import at the top
from .analyzers.mytool import analyze_mytool
# Add to analyzers list
analyzers = [
(analyze_binwalk, img_path, result_path),
(analyze_exiftool, img_path, result_path),
# ... other analyzers ...
(analyze_mytool, img_path, result_path),
]
# If it needs a password, pass submission.password:
# (analyze_mytool, img_path, result_path, submission.password),
# If only for deep analysis, add it conditionally:
if submission.deep_analysis:
analyzers.extend([
(analyze_mytool, img_path, result_path),
])
```
## Available Methods to Override
| Method | Purpose | Default Behavior |
|--------|---------|------------------|
| `build_cmd()` | Build command to run | Uses `self.cmd` |
| `is_error()` | Check if result is error | Returns `True` if stderr exists |
| `process_output()` | Format success output | Returns stdout as list of lines |
| `process_error()` | Format error message | Returns stderr as-is |
| `process_note()` | Add informational note | Returns `None` |
| `get_extracted_dir()` | Set extraction directory | Returns `output_dir/toolname/` |
## Output Format
Your analyzer produces JSON in `results.json`:
**Success:**
```json
{
"mytool": {
"status": "ok",
"output": "Tool output here",
"download": "/download/hash/mytool"
}
}
```
**Error:**
```json
{
"mytool": {
"status": "error",
"error": "Error message"
}
}
```
## Tips
- Use `self.img` in commands (relative path), not `self.input_img`
- Set `has_archive=True` if your tool extracts files
- Tools run in parallel threads - no need to worry about concurrency
- The base class handles timeouts (10 minutes default)
- Extracted files are automatically zipped into `.7z` archives
- All exceptions are caught and logged to Sentry
## Example: Complete Analyzer
```python
from pathlib import Path
from .base_analyzer import SubprocessAnalyzer
class PngcheckAnalyzer(SubprocessAnalyzer):
def __init__(self, input_img: Path, output_dir: Path) -> None:
super().__init__("pngcheck", input_img, output_dir)
self.cmd = ["pngcheck", "-v", self.img]
def is_error(self, returncode: int, stdout: str, stderr: str, zip_exist: bool) -> bool:
return "this is neither a PNG or JNG image" in stdout
def process_error(self, stdout: str, stderr: str) -> str:
if "neither a PNG or JNG" in stdout:
return "File format not supported (PNG/JNG only)"
return stdout
def analyze_pngcheck(input_img: Path, output_dir: Path) -> None:
analyzer = PngcheckAnalyzer(input_img, output_dir)
analyzer.analyze()
```
That's it! Your analyzer will now run in parallel with the others and results will appear in the web interface.