sast 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sast/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ """sast — thin launcher for the Insomnia SAST binary.
2
+
3
+ `pip install sast` lays down only this tiny pure-Python launcher. The actual
4
+ SAST engine is a prebuilt, self-contained binary that is fetched on first run
5
+ from insom.ai, matched to your OS, checksum-verified, and cached. Subsequent
6
+ runs exec the cached binary directly.
7
+ """
8
+
9
+ __version__ = "0.1.0"
sast/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Allow `python -m sast` to behave like the `sast` console script."""
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())
sast/_download.py ADDED
@@ -0,0 +1,264 @@
1
+ """OS detection, manifest fetch, binary download + checksum + cache.
2
+
3
+ Pure stdlib (urllib/hashlib/platform) so the wheel has zero runtime deps and
4
+ stays a few KB. The hosted layout this expects on insom.ai:
5
+
6
+ https://insom.ai/static/downloads/sast/manifest.json
7
+
8
+ {
9
+ "version": "2026.06.04-abc1234",
10
+ "platforms": {
11
+ "linux": {"url": ".../sast-linux-x64", "sha256": "<hex>"},
12
+ "macos": {"url": ".../sast-macos-x64", "sha256": "<hex>"},
13
+ "windows": {"url": ".../sast-windows-x64.exe", "sha256": "<hex>"}
14
+ }
15
+ }
16
+
17
+ `url` may be absolute or relative to the manifest's own URL. `sha256` is
18
+ optional but strongly recommended — when present it is enforced.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import hashlib
24
+ import os
25
+ import platform
26
+ import stat
27
+ import sys
28
+ import urllib.request
29
+ from urllib.parse import urljoin
30
+
31
+ # Override with the SAST_MANIFEST_URL env var (handy for staging / self-hosting).
32
+ DEFAULT_MANIFEST_URL = "https://insom.ai/static/downloads/sast/manifest.json"
33
+
34
+ _USER_AGENT = "sast-launcher"
35
+
36
+
37
+ class DownloadError(RuntimeError):
38
+ """Raised when the binary cannot be fetched or verified."""
39
+
40
+
41
+ def manifest_url() -> str:
42
+ return os.environ.get("SAST_MANIFEST_URL", DEFAULT_MANIFEST_URL).strip()
43
+
44
+
45
+ def detect_platform() -> str:
46
+ """Map the host OS to a manifest platform key (linux / macos / windows)."""
47
+ system = platform.system().lower()
48
+ if system.startswith("linux"):
49
+ return "linux"
50
+ if system == "darwin":
51
+ return "macos"
52
+ if system.startswith("win"):
53
+ return "windows"
54
+ raise DownloadError(
55
+ f"Unsupported operating system: {platform.system()!r}. "
56
+ "sast ships binaries for Linux, macOS and Windows only."
57
+ )
58
+
59
+
60
+ def _arch_is_supported() -> bool:
61
+ """The hosted binaries are x86-64 only (for now). arm64 macs run via Rosetta."""
62
+ machine = platform.machine().lower()
63
+ return machine in {"x86_64", "amd64", "x64"} or platform.system().lower() == "darwin"
64
+
65
+
66
+ def cache_dir() -> str:
67
+ """Per-user cache directory for the downloaded binary, by OS convention."""
68
+ override = os.environ.get("SAST_CACHE_DIR")
69
+ if override:
70
+ return override
71
+ system = platform.system().lower()
72
+ if system.startswith("win"):
73
+ base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~\\AppData\\Local")
74
+ return os.path.join(base, "sast", "bin")
75
+ if system == "darwin":
76
+ return os.path.expanduser("~/Library/Application Support/sast/bin")
77
+ base = os.environ.get("XDG_CACHE_HOME") or os.path.expanduser("~/.cache")
78
+ return os.path.join(base, "sast", "bin")
79
+
80
+
81
+ def binary_path() -> str:
82
+ name = "sast.exe" if detect_platform() == "windows" else "sast"
83
+ return os.path.join(cache_dir(), name)
84
+
85
+
86
+ def _version_marker() -> str:
87
+ return os.path.join(cache_dir(), ".version")
88
+
89
+
90
+ def _lastcheck_marker() -> str:
91
+ return os.path.join(cache_dir(), ".lastcheck")
92
+
93
+
94
+ def _update_interval() -> int:
95
+ """Seconds between background 'is a newer engine available?' checks.
96
+
97
+ Default 24h. Set SAST_UPDATE_INTERVAL=0 to disable auto-update entirely
98
+ (the cached binary is then used until `sast self-update` is run).
99
+ """
100
+ raw = os.environ.get("SAST_UPDATE_INTERVAL", "").strip()
101
+ if not raw:
102
+ return 86400
103
+ try:
104
+ return max(0, int(raw))
105
+ except ValueError:
106
+ return 86400
107
+
108
+
109
+ def _update_check_due() -> bool:
110
+ interval = _update_interval()
111
+ if interval <= 0:
112
+ return False
113
+ import time
114
+
115
+ try:
116
+ last = os.path.getmtime(_lastcheck_marker())
117
+ except OSError:
118
+ return True # never checked
119
+ return (time.time() - last) >= interval
120
+
121
+
122
+ def _mark_checked() -> None:
123
+ try:
124
+ os.makedirs(cache_dir(), exist_ok=True)
125
+ with open(_lastcheck_marker(), "w", encoding="utf-8") as fh:
126
+ fh.write("")
127
+ except OSError:
128
+ pass
129
+
130
+
131
+ def _http_get(url: str, *, binary: bool) -> bytes:
132
+ import ssl
133
+
134
+ req = urllib.request.Request(url, headers={"User-Agent": _USER_AGENT})
135
+ try:
136
+ with urllib.request.urlopen(req, timeout=120) as resp: # noqa: S310 (https only by default)
137
+ return resp.read()
138
+ except ssl.SSLCertVerificationError as exc:
139
+ hint = ""
140
+ if platform.system().lower() == "darwin":
141
+ # Classic python.org-build issue: the bundled OpenSSL has no CA store
142
+ # until the user runs the post-install "Install Certificates.command".
143
+ hint = (
144
+ "\nOn macOS this usually means your Python install has no CA "
145
+ "certificates. Run:\n"
146
+ ' /Applications/Python\\ 3.x/Install\\ Certificates.command\n'
147
+ "or: pip install --upgrade certifi"
148
+ )
149
+ raise DownloadError(f"TLS certificate verification failed for {url}: {exc}{hint}") from exc
150
+ except Exception as exc: # urllib raises a zoo of exception types
151
+ raise DownloadError(f"Could not fetch {url}: {exc}") from exc
152
+
153
+
154
+ def _load_manifest() -> dict:
155
+ import json
156
+
157
+ raw = _http_get(manifest_url(), binary=False)
158
+ try:
159
+ data = json.loads(raw.decode("utf-8"))
160
+ except Exception as exc:
161
+ raise DownloadError(f"Manifest at {manifest_url()} is not valid JSON: {exc}") from exc
162
+ if not isinstance(data, dict) or "platforms" not in data:
163
+ raise DownloadError("Manifest is missing the required 'platforms' object.")
164
+ return data
165
+
166
+
167
+ def _verify_sha256(blob: bytes, expected: str) -> None:
168
+ actual = hashlib.sha256(blob).hexdigest()
169
+ if actual.lower() != expected.lower():
170
+ raise DownloadError(
171
+ "Checksum mismatch — refusing to install a tampered or corrupt binary.\n"
172
+ f" expected sha256: {expected}\n"
173
+ f" actual sha256: {actual}"
174
+ )
175
+
176
+
177
+ def current_version() -> str | None:
178
+ try:
179
+ with open(_version_marker(), encoding="utf-8") as fh:
180
+ return fh.read().strip() or None
181
+ except OSError:
182
+ return None
183
+
184
+
185
+ def ensure_binary(*, force: bool = False, quiet: bool = False) -> str:
186
+ """Return the path to the cached binary, downloading it if needed.
187
+
188
+ Behaviour:
189
+ * missing binary -> download it (first run).
190
+ * force=True -> always re-fetch the latest (used by `sast self-update`).
191
+ * binary present -> reused immediately. At most once per
192
+ SAST_UPDATE_INTERVAL (default 24h) it also checks insom.ai for a newer
193
+ version and upgrades automatically. Network failures fail open: the
194
+ cached binary keeps working offline.
195
+ """
196
+ path = binary_path()
197
+ exists = os.path.exists(path)
198
+
199
+ manifest = None
200
+ if exists and not force:
201
+ if not _update_check_due():
202
+ return path
203
+ # An update check is due — see whether insom.ai has a newer build.
204
+ try:
205
+ manifest = _load_manifest()
206
+ except DownloadError:
207
+ _mark_checked() # offline / server down: keep using the cached binary
208
+ return path
209
+ _mark_checked()
210
+ latest = manifest.get("version") or ""
211
+ if latest == (current_version() or ""):
212
+ return path # already on the latest
213
+ _warn(quiet, f"sast: newer engine available ({latest}); updating...")
214
+
215
+ if not _arch_is_supported():
216
+ _warn(
217
+ quiet,
218
+ f"warning: CPU architecture {platform.machine()!r} has no native sast build; "
219
+ "attempting the x86-64 binary.",
220
+ )
221
+
222
+ plat = detect_platform()
223
+ if manifest is None:
224
+ _warn(quiet, "sast: fetching the SAST engine (first run)..." if not force
225
+ else "sast: updating the SAST engine...")
226
+ manifest = _load_manifest()
227
+
228
+ entry = manifest.get("platforms", {}).get(plat)
229
+ if not entry or not entry.get("url"):
230
+ raise DownloadError(f"Manifest has no download entry for platform {plat!r}.")
231
+
232
+ url = urljoin(manifest_url(), entry["url"])
233
+ blob = _http_get(url, binary=True)
234
+
235
+ sha = entry.get("sha256")
236
+ if sha:
237
+ _verify_sha256(blob, sha)
238
+ else:
239
+ _warn(quiet, "warning: manifest provided no sha256 — skipping integrity check.")
240
+
241
+ os.makedirs(cache_dir(), exist_ok=True)
242
+ tmp = path + ".part"
243
+ with open(tmp, "wb") as fh:
244
+ fh.write(blob)
245
+ if plat != "windows":
246
+ mode = os.stat(tmp).st_mode
247
+ os.chmod(tmp, mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
248
+ os.replace(tmp, path)
249
+
250
+ version = manifest.get("version", "")
251
+ try:
252
+ with open(_version_marker(), "w", encoding="utf-8") as fh:
253
+ fh.write(version)
254
+ except OSError:
255
+ pass
256
+ _mark_checked()
257
+
258
+ _warn(quiet, f"sast: installed engine {version or '(unversioned)'} -> {path}")
259
+ return path
260
+
261
+
262
+ def _warn(quiet: bool, msg: str) -> None:
263
+ if not quiet:
264
+ print(msg, file=sys.stderr)
sast/cli.py ADDED
@@ -0,0 +1,71 @@
1
+ """`sast` entry point: ensure the engine binary is present, then hand off to it.
2
+
3
+ All arguments after `sast` are passed straight through to the underlying SAST
4
+ binary, so `sast --help`, `sast <path> --sarif`, etc. behave exactly like the
5
+ native CLI. A few launcher-only subcommands are intercepted first.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import subprocess
12
+ import sys
13
+
14
+ from . import __version__
15
+ from ._download import (
16
+ DownloadError,
17
+ binary_path,
18
+ current_version,
19
+ ensure_binary,
20
+ )
21
+
22
+ _LAUNCHER_HELP = f"""sast {__version__} — launcher for the Insomnia SAST engine
23
+
24
+ Usage:
25
+ sast [SAST-ARGS...] Run the SAST engine (downloads it on first use).
26
+ sast self-update Re-download the latest engine binary.
27
+ sast self-version Show launcher + cached-engine versions.
28
+ sast self-where Print the path to the cached engine binary.
29
+
30
+ Everything else is forwarded to the engine. Try: sast --help
31
+ """
32
+
33
+
34
+ def _run_engine(args: list[str]) -> int:
35
+ path = ensure_binary()
36
+ # On POSIX, exec replaces this process so signals/exit codes pass through
37
+ # cleanly. On Windows there is no exec, so spawn and forward the exit code.
38
+ if os.name == "posix":
39
+ os.execv(path, [path, *args]) # never returns
40
+ completed = subprocess.run([path, *args])
41
+ return completed.returncode
42
+
43
+
44
+ def main(argv: list[str] | None = None) -> int:
45
+ argv = list(sys.argv[1:] if argv is None else argv)
46
+
47
+ if argv:
48
+ cmd = argv[0]
49
+ if cmd == "self-update":
50
+ ensure_binary(force=True)
51
+ return 0
52
+ if cmd == "self-version":
53
+ print(f"sast launcher {__version__}")
54
+ print(f"engine {current_version() or '(not yet installed)'}")
55
+ return 0
56
+ if cmd == "self-where":
57
+ print(binary_path())
58
+ return 0
59
+ if cmd in ("self-help", "--launcher-help"):
60
+ print(_LAUNCHER_HELP)
61
+ return 0
62
+
63
+ try:
64
+ return _run_engine(argv)
65
+ except DownloadError as exc:
66
+ print(f"sast: {exc}", file=sys.stderr)
67
+ return 3
68
+
69
+
70
+ if __name__ == "__main__":
71
+ raise SystemExit(main())
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.4
2
+ Name: sast
3
+ Version: 0.1.0
4
+ Summary: sast — free, fast static application security testing for CI/CD. Installs a self-contained SAST engine (17+ languages, taint tracking, secrets, IaC, SCA; HTML/JSON/SARIF) on first run.
5
+ Author: CQR Cybersecurity LLC
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://insom.ai
8
+ Keywords: sast,security,static-analysis,sca,sarif,ci,devsecops
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Security
13
+ Classifier: Topic :: Software Development :: Quality Assurance
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Dynamic: license-file
20
+
21
+ # sast
22
+
23
+ **Free, fast static application security testing for CI/CD.**
24
+
25
+ `sast` is a tiny launcher. Installing it is instant; the first time you run it,
26
+ it downloads a self-contained SAST engine binary that matches your operating
27
+ system, verifies its checksum, and caches it. Every run after that is native
28
+ speed with no Python dependencies.
29
+
30
+ ```bash
31
+ pip install sast
32
+ sast . # scan the current directory
33
+ sast ./src --sarif report.sarif
34
+ sast --help # full engine options
35
+ ```
36
+
37
+ > Supports Linux, macOS and Windows (x86-64). On Apple Silicon the macOS
38
+ > binary runs under Rosetta.
39
+
40
+ ## What it scans
41
+
42
+ - **SAST** across 17+ languages with taint tracking
43
+ - **Secrets** detection (entropy + vendor rule packs)
44
+ - **IaC / cloud misconfiguration** (Terraform, K8s, Docker, …)
45
+ - **SCA** — known-vulnerable dependencies
46
+ - Output as **HTML**, **JSON**, or **SARIF** (drops straight into GitHub code scanning)
47
+
48
+ ## How it works
49
+
50
+ `pip install sast` lays down only a few KB of pure-Python launcher — **no
51
+ download happens at install time** (that keeps offline/CI installs reliable).
52
+ On first invocation the launcher:
53
+
54
+ 1. Detects your OS → `linux` / `macos` / `windows`.
55
+ 2. Fetches the manifest from `https://insom.ai/static/downloads/sast/manifest.json`.
56
+ 3. Downloads the matching binary and verifies its `sha256`.
57
+ 4. Caches it under your per-user cache directory and `exec`s it.
58
+
59
+ Because the engine lives on the server, new engine releases reach users
60
+ without republishing the pip package.
61
+
62
+ ### Staying on the latest engine
63
+
64
+ After the first download the cached binary is reused for speed. At most once
65
+ per day (`SAST_UPDATE_INTERVAL`, default `86400` seconds) `sast` also asks
66
+ insom.ai whether a newer engine is published and, if so, upgrades itself
67
+ automatically. Update checks **fail open** — if you're offline or the server
68
+ is unreachable, the cached binary keeps working. Set `SAST_UPDATE_INTERVAL=0`
69
+ to pin the cached version, or run `sast self-update` to force the latest at
70
+ any time.
71
+
72
+ ## Launcher commands
73
+
74
+ | Command | What it does |
75
+ |---------------------|-----------------------------------------------|
76
+ | `sast …` | Forward all args to the SAST engine |
77
+ | `sast self-update` | Re-download the latest engine binary |
78
+ | `sast self-version` | Show launcher + cached-engine versions |
79
+ | `sast self-where` | Print the cached binary path |
80
+
81
+ ## Environment variables
82
+
83
+ | Variable | Purpose |
84
+ |-------------------------|------------------------------------------------------------|
85
+ | `SAST_MANIFEST_URL` | Override the manifest URL (staging / self-hosting) |
86
+ | `SAST_CACHE_DIR` | Override where the binary is cached |
87
+ | `SAST_UPDATE_INTERVAL` | Seconds between auto-update checks (default `86400`; `0` disables) |
88
+
89
+ Default cache locations:
90
+
91
+ - **Linux:** `~/.cache/sast/bin`
92
+ - **macOS:** `~/Library/Application Support/sast/bin`
93
+ - **Windows:** `%LOCALAPPDATA%\sast\bin`
94
+
95
+ ## Use in CI (GitHub Actions)
96
+
97
+ ```yaml
98
+ - run: pip install sast
99
+ - run: sast . --sarif results.sarif --fail-on high
100
+ - uses: github/codeql-action/upload-sarif@v3
101
+ with:
102
+ sarif_file: results.sarif
103
+ ```
104
+
105
+ `sast` exits non-zero when findings meet your `--fail-on` threshold, failing
106
+ the build.
107
+
108
+ ## Server-side manifest format
109
+
110
+ The launcher expects this JSON at `SAST_MANIFEST_URL`:
111
+
112
+ ```json
113
+ {
114
+ "version": "2026.06.04-abc1234",
115
+ "platforms": {
116
+ "linux": { "url": "sast-linux-x64", "sha256": "<hex>" },
117
+ "macos": { "url": "sast-macos-x64", "sha256": "<hex>" },
118
+ "windows": { "url": "sast-windows-x64.exe", "sha256": "<hex>" }
119
+ }
120
+ }
121
+ ```
122
+
123
+ `url` may be relative to the manifest URL or absolute. `sha256` is optional but
124
+ enforced when present.
125
+
126
+ ---
127
+
128
+ © CQR Cybersecurity LLC. The `sast` launcher is open source; the SAST engine
129
+ binary it downloads is proprietary. See <https://insom.ai>.
@@ -0,0 +1,10 @@
1
+ sast/__init__.py,sha256=h1EO8NTKoKdvOmoAv0AagYQHDe5-XOJuU1qzpjQAe5Y,354
2
+ sast/__main__.py,sha256=4ARGWRWoiMKKgxYtTsl3Vyl5Dy3yKdNaqkx3QNNyHAU,151
3
+ sast/_download.py,sha256=VfqocCiiDsBS5_bLfMzY45MdTPVoeP22iWCCD6HYasE,8808
4
+ sast/cli.py,sha256=fQnKFAmiF3RkqjT975DnKKSySAunwYkIMrh7URJjIMM,2147
5
+ sast-0.1.0.dist-info/licenses/LICENSE,sha256=Op0BssnKeTi7SZWQZDAYa9TQnTnTketj4RawE1nHW8g,1368
6
+ sast-0.1.0.dist-info/METADATA,sha256=0pCl3PLnbHnJWrw6kmkOH_4YuXKbRTD8u2WB4z0LCz4,4825
7
+ sast-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
8
+ sast-0.1.0.dist-info/entry_points.txt,sha256=F-3hNUrsGdBB3J6QAPHBRHHigKTpS0sM81ms42i-9G0,39
9
+ sast-0.1.0.dist-info/top_level.txt,sha256=M4oQT0X5raRTlKrbP6SBWFO-9ui8rLTNMCpQfniPHEQ,5
10
+ sast-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sast = sast.cli:main
@@ -0,0 +1,26 @@
1
+ MIT License (launcher only)
2
+
3
+ Copyright (c) 2026 CQR Cybersecurity LLC
4
+
5
+ This license applies ONLY to the source code in this repository (the "sast"
6
+ launcher). It does NOT apply to the Insomnia SAST engine binary that the
7
+ launcher downloads at runtime, which is proprietary software of CQR
8
+ Cybersecurity LLC and is provided under separate terms.
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ sast