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
13 changes: 12 additions & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
### Related Issues

<!-- Link to the issues that are solved with this PR. -->
<!-- Link to the issues that are solved with this PR (e.g. Closes #123) -->

### Purpose

Expand All @@ -9,3 +9,14 @@
### Approach

<!--- How does this address the problem? -->

### 🛡️ Pre-Merge Contributor Checklist

Please verify that your PR fulfills all the following mandatory requirements before requesting review:

- [ ] **Changelog**: I have documented my changes under the `[Unreleased]` section in `CHANGELOG.md`. *(Mandatory for all code modifications)*
- [ ] **Tests**: I have added unit or integration tests verifying my changes.
- [ ] **Lints & Style**: I have run `ruff check .` locally and resolved all formatting or style violations.
- [ ] **Type Check**: I have verified my type annotations pass local type checking hooks.
- [ ] **Conventional Commits**: My commits follow the standard semantic guidelines (e.g. `feat:`, `fix:`, `docs:`, `test:`).
- [ ] **Security Scan**: I have confirmed that no insecure functions (`eval`, unescaped `subprocess.call` with `shell=True`) were introduced.
79 changes: 79 additions & 0 deletions .github/workflows/esim-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: eSim Code Quality & Testing CI

on:
push:
branches: [ master, dev, main ]
pull_request:
branches: [ master, dev, main ]

jobs:
static-analysis:
name: 🛡️ Static Analysis & Lints
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'

- name: Install Linting & Security Tools
run: |
python -m pip install --upgrade pip
pip install ruff pyright bandit semgrep

- name: Run Ruff (Formatting & Style)
run: ruff check .

- name: Run Pyright (Strict Type Checking)
run: pyright src/
continue-on-error: true # Non-blocking initially to allow progressive adoption

- name: Run Bandit (Security Vulnerability Scan)
run: bandit -r src/ -ll -ii

test-suite:
name: 🧪 Headless Test Suite
needs: static-analysis
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11']
fail-fast: false
steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Install System Dependencies
run: |
sudo apt-get update
sudo apt-get install -y ngspice xvfb python3-pyqt6 libegl1-mesa-dev

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'

- name: Install eSim Dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
pip install pytest pytest-cov pytest-qt numpy

- name: Run Unit Tests (Headless)
env:
QT_QPA_PLATFORM: offscreen
run: |
xvfb-run --server-args="-screen 0 1024x768x24" pytest --cov=src --cov-report=xml tests/

- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
flags: unittests
fail_ci_if_error: false
16 changes: 16 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-added-large-files
- id: check-merge-conflict

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.4
hooks:
- id: ruff
args: [ --fix ]
- id: ruff-format
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Changelog
All notable changes to the eSim project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- Created `/tests/test_security_p0.py` verifying all 26 edge cases for AST-based expression evaluation and subprocess sandboxing.
- Implemented Phase 1 DevOps automation pipeline (`.github/workflows/esim-ci.yml`), establishing Ruff, Pyright, and headless pytest-qt runner rules.
- Set up local pre-commit hooks configuration (`.pre-commit-config.yaml`) and central tool settings (`pyproject.toml`).

### Changed
- **PR #506 Conflict Resolution**: Restored our advanced, regex-based placeholder expression evaluator inside `plot_function` to safely compute traces containing parentheses (such as `v(out)`).

### Fixed
- **VULN-01 (P0 - Critical)**: Eliminated arbitrary code execution by replacing raw `eval()` in `plot_window.py` with a robust, whitelisted AST expression parser supporting standard mathematical operations (`np.sin`, `np.cos`, `np.log`, etc.).
- **VULN-02 (P0 - Critical)**: Eliminated shell injection in `pspiceToKicad.py` by converting `subprocess.run(shell=True)` to standard list-of-arguments process calling via `sys.executable`.
- **VULN-03 (P1 - High)**: Hardened `ngspice_ghdl.py` by converting vulnerable `subprocess.call(..., shell=True)` invocations to use safe list-of-arguments process execution and `shutil.rmtree`.
97 changes: 97 additions & 0 deletions esim_mac.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# -*- mode: python ; coding: utf-8 -*-

import os
import sys

block_cipher = None

# Base path of the repository
base_dir = os.path.abspath(os.getcwd())

a = Analysis(
['src/frontEnd/Application.py'],
pathex=[os.path.join(base_dir, 'src')],
binaries=[],
datas=[
('library', 'library'),
('images', 'images'),
('VERSION', '.'),
],
hiddenimports=[
'frontEnd',
'frontEnd.pathmagic',
'frontEnd.ProjectExplorer',
'frontEnd.Workspace',
'frontEnd.DockArea',
'projManagement',
'projManagement.openProject',
'projManagement.newProject',
'projManagement.Kicad',
'projManagement.Validation',
'projManagement.Worker',
'kicadtoNgspice',
'kicadtoNgspice.DeviceModel',
'kicadtoNgspice.Processing',
'kicadtoNgspice.SubcircuitTab',
'maker',
'maker.Maker',
'maker.ModelGeneration',
'maker.NgVeri',
'ngspiceSimulation',
'ngspiceSimulation.NgspiceWidget',
'browser',
'browser.Welcome',
'browser.UserManual',
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='eSim',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='eSim',
)

app = BUNDLE(
coll,
name='eSim.app',
icon=os.path.join(base_dir, 'images', 'logo.icns'),
bundle_identifier='org.fossee.esim',
info_plist={
'NSPrincipalClass': 'NSApplication',
'NSAppleScriptEnabled': False,
'CFBundleDocumentTypes': [],
},
)
Binary file added images/logo.icns
Binary file not shown.
26 changes: 8 additions & 18 deletions nghdl/src/ngspice_ghdl.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,7 @@ def createModelDirectory(self):
)
if ret == QtWidgets.QMessageBox.Ok:
print("Overwriting existing model " + self.modelname)
if os.name == 'nt':
cmd = "rmdir " + self.modelname + "/s /q"
else:
cmd = "rm -rf " + self.modelname
# process = subprocess.Popen(
# cmd, stdout=subprocess.PIPE,
# stderr=subprocess.PIPE, shell=True
# )
subprocess.call(cmd, shell=True)
shutil.rmtree(self.modelname, ignore_errors=True)
os.mkdir(self.modelname)
else:
print("Exiting application")
Expand Down Expand Up @@ -232,16 +224,14 @@ def createModelFiles(self):
if os.name == 'nt':
# path to msys bin directory where bash is located
self.msys_home = self.parser.get('COMPILER', 'MSYS_HOME')
subprocess.call(self.msys_home + "/usr/bin/bash.exe " +
path + "/DUTghdl/compile.sh", shell=True)
subprocess.call(self.msys_hoscme + "/usr/bin/bash.exe -c " +
"'chmod a+x start_server.sh'", shell=True)
subprocess.call(self.msys_home + "/usr/bin/bash.exe -c " +
"'chmod a+x sock_pkg_create.sh'", shell=True)
bash_exe = os.path.join(self.msys_home, "usr", "bin", "bash.exe")
subprocess.call([bash_exe, os.path.join(path, "DUTghdl", "compile.sh")])
subprocess.call([bash_exe, "-c", "chmod a+x start_server.sh"])
subprocess.call([bash_exe, "-c", "chmod a+x sock_pkg_create.sh"])
else:
subprocess.call("bash " + path + "/DUTghdl/compile.sh", shell=True)
subprocess.call("chmod a+x start_server.sh", shell=True)
subprocess.call("chmod a+x sock_pkg_create.sh", shell=True)
subprocess.call(["bash", os.path.join(path, "DUTghdl", "compile.sh")])
subprocess.call(["chmod", "a+x", "start_server.sh"])
subprocess.call(["chmod", "a+x", "sock_pkg_create.sh"])

os.remove("compile.sh")
# os.remove("ghdlserver.c")
Expand Down
36 changes: 36 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[tool.ruff]
line-length = 100
target-version = "py311"
exclude = [
".git",
".github",
".pytest_cache",
"build",
"dist",
]

[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP", "ASYNC", "B", "A", "C4", "SIM", "RET"]
ignore = [
"E501", # Line too long (handled by formatting)
"RET505", # Unnecessary else after return
]

[tool.pyright]
include = ["src"]
exclude = ["**/node_modules", "**/__pycache__"]
pythonVersion = "3.11"
reportMissingImports = true
reportMissingTypeStubs = false
typeCheckingMode = "basic"

[tool.pytest.ini_options]
minversion = "6.0"
addopts = "-ra -q --tb=short"
testpaths = [
"tests",
]
python_files = "test_*.py"
filterwarnings = [
"ignore::DeprecationWarning",
]
78 changes: 78 additions & 0 deletions scripts/build_macos_dmg.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env bash

# Exit immediately if a command exits with a non-zero status
set -e

echo "========================================================="
echo " Starting eSim Standalone macOS Packaging "
echo "========================================================="

# 1. Dependency Checks: PyInstaller
if ! python3 -c "import PyInstaller" &> /dev/null; then
echo "Installing PyInstaller..."
pip3 install pyinstaller
else
echo "PyInstaller is already installed."
fi

# 2. Dependency Checks: create-dmg (Homebrew)
if ! which create-dmg &> /dev/null; then
echo "Installing create-dmg via Homebrew..."
if which brew &> /dev/null; then
brew install create-dmg
else
echo "Error: Homebrew is not installed. Please install Homebrew or install 'create-dmg' manually."
exit 1
fi
else
echo "create-dmg is already installed."
fi

# 3. Create macOS .icns file dynamically from logo.png
echo "Generating macOS .icns application icon..."
if [ -f "images/logo.png" ]; then
mkdir -p logo.iconset
sips -z 16 16 images/logo.png --out logo.iconset/icon_16x16.png
sips -z 32 32 images/logo.png --out logo.iconset/icon_16x16@2x.png
sips -z 32 32 images/logo.png --out logo.iconset/icon_32x32.png
sips -z 64 64 images/logo.png --out logo.iconset/icon_32x32@2x.png
sips -z 128 128 images/logo.png --out logo.iconset/icon_128x128.png
sips -z 256 256 images/logo.png --out logo.iconset/icon_128x128@2x.png
sips -z 256 256 images/logo.png --out logo.iconset/icon_256x256.png
sips -z 512 512 images/logo.png --out logo.iconset/icon_256x256@2x.png
sips -z 512 512 images/logo.png --out logo.iconset/icon_512x512.png
sips -z 1024 1024 images/logo.png --out logo.iconset/icon_512x512@2x.png
iconutil -c icns logo.iconset
mv logo.icns images/logo.icns
rm -rf logo.iconset
echo "Application icon successfully created at images/logo.icns."
else
echo "Warning: images/logo.png not found. Bundling without custom icon."
fi

# 4. Compile Standalone macOS Application Bundle (.app)
echo "Compiling Standalone eSim.app..."
rm -rf dist/eSim dist/eSim.app build/esim_mac
pyinstaller --clean -y esim_mac.spec

# 5. Build Drag-and-Drop .dmg Installer Disk Image
echo "Packaging Standalone eSim.dmg installer..."
if [ -f "dist/eSim.dmg" ]; then
rm "dist/eSim.dmg"
fi

create-dmg \
--volname "eSim Installer" \
--volicon "images/logo.icns" \
--window-pos 200 120 \
--window-size 600 400 \
--icon-size 100 \
--icon "eSim.app" 175 190 \
--hide-extension "eSim.app" \
--app-drop-link 425 190 \
"dist/eSim.dmg" \
"dist/"

echo "========================================================="
echo " 🎉 Success! eSim Standalone DMG built at: dist/eSim.dmg"
echo "========================================================="
Loading
Loading