S-Dreamer commited on
Commit
c831574
·
verified ·
1 Parent(s): 468177f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +289 -98
app.py CHANGED
@@ -1,131 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- def generate_code(prompt: str, language: str, style: str, include_tests: bool) -> str:
4
- tests_note = "Include tests" if include_tests else "No tests requested"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- return f"""# Language: {language}
7
- # Style: {style}
8
- # {tests_note}
 
 
 
 
 
9
 
10
- # Generated Code for:
11
- # {prompt}
12
- """
13
 
14
- def refine_code(existing_code: str, refinement_prompt: str, language: str) -> str:
15
- return f"""# Refined {language} Code
16
- # Refinement request:
17
- # {refinement_prompt}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- {existing_code}
20
 
21
- # Applied refinement:
22
- # {refinement_prompt}
23
- """
24
 
25
  st.set_page_config(
26
- page_title="Code Assistant",
27
- page_icon="💻",
28
- layout="wide"
29
  )
30
 
31
- if "prompt" not in st.session_state:
32
- st.session_state["prompt"] = ""
33
 
34
- if "generated_code" not in st.session_state:
35
- st.session_state["generated_code"] = ""
36
-
37
- st.title("Code Assistant")
38
- st.caption("Generate, refine, and export code from natural-language prompts.")
39
 
40
  with st.sidebar:
41
- st.header("Settings")
42
 
43
- language = st.selectbox(
44
- "Language",
45
- ["Python", "JavaScript", "TypeScript", "SQL", "Bash", "HTML/CSS"]
46
  )
47
-
48
- style = st.selectbox(
49
- "Output style",
50
- ["Clean and simple", "Beginner-friendly", "Production-ready", "Heavily commented"]
51
  )
52
 
53
- include_tests = st.checkbox("Include tests", value=False)
54
 
55
- st.divider()
56
- st.header("Prompt examples")
 
57
 
58
- examples = [
59
- "Write a Python function that validates email addresses and includes tests.",
60
- "Create a Streamlit app that uploads a CSV and plots summary statistics.",
61
- "Refactor this JavaScript function to be easier to read.",
62
- "Write a SQL query to find monthly recurring revenue by customer."
63
- ]
 
 
 
64
 
65
- for i, example in enumerate(examples):
66
- if st.button(example, key=f"example_{i}"):
67
- st.session_state["prompt"] = example
68
 
69
- prompt = st.text_area(
70
- "Coding prompt",
71
- key="prompt",
72
- placeholder="Describe the code you want generated...",
73
- height=170
74
  )
75
 
76
- generate_clicked = st.button("Generate", type="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
- if generate_clicked:
79
- if not prompt.strip():
80
- st.warning("Please enter a coding prompt first.")
81
  else:
82
- with st.spinner("Generating code..."):
83
- st.session_state["generated_code"] = generate_code(
84
- prompt,
85
- language,
86
- style,
87
- include_tests
88
- )
89
 
90
- if st.session_state["generated_code"]:
91
- st.subheader("Generated output")
 
 
 
92
 
93
- tab_code, tab_refine, tab_actions = st.tabs(["Code", "Refine", "Actions"])
 
 
94
 
95
- with tab_code:
96
- st.code(st.session_state["generated_code"], language=language.lower())
97
 
98
- with tab_refine:
99
- refinement_prompt = st.text_area(
100
- "Refinement request",
101
- placeholder="Example: Add error handling, make it async, improve readability, or add type hints.",
102
- height=120
103
- )
104
 
105
- if st.button("Apply refinement", type="primary"):
106
- if not refinement_prompt.strip():
107
- st.warning("Please enter a refinement request first.")
108
- else:
109
- with st.spinner("Refining code..."):
110
- st.session_state["generated_code"] = refine_code(
111
- st.session_state["generated_code"],
112
- refinement_prompt,
113
- language
114
- )
115
-
116
- st.success("Refinement applied.")
117
- st.rerun()
118
-
119
- with tab_actions:
120
- st.download_button(
121
- label="Download code",
122
- data=st.session_state["generated_code"],
123
- file_name="generated_code.txt",
124
- mime="text/plain"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  )
126
 
127
- if st.button("Clear output"):
128
- st.session_state["generated_code"] = ""
129
- st.rerun()
130
- else:
131
- st.info("Choose an example or enter your own prompt to begin.")
 
 
 
 
 
1
+ # app.py
2
+ # Authorized Security Assessment Workflow
3
+ # Run: streamlit run app.py
4
+
5
+ from __future__ import annotations
6
+
7
+ import socket
8
+ import ssl
9
+ from datetime import datetime, timezone
10
+ from urllib.parse import urlparse, urljoin
11
+
12
+ import httpx
13
  import streamlit as st
14
+ from bs4 import BeautifulSoup
15
+ from pydantic import BaseModel, Field, HttpUrl
16
+
17
+
18
+ class Scope(BaseModel):
19
+ target_url: HttpUrl
20
+ authorization_confirmed: bool
21
+ engagement_notes: str = ""
22
+
23
+
24
+ class Finding(BaseModel):
25
+ title: str
26
+ severity: str
27
+ evidence: str
28
+ recommendation: str
29
+
30
+
31
+ SECURITY_HEADERS = [
32
+ "strict-transport-security",
33
+ "content-security-policy",
34
+ "x-frame-options",
35
+ "x-content-type-options",
36
+ "referrer-policy",
37
+ "permissions-policy",
38
+ ]
39
+
40
+
41
+ def utc_now() -> str:
42
+ return datetime.now(timezone.utc).isoformat()
43
+
44
+
45
+ def fetch_url(url: str) -> dict:
46
+ try:
47
+ with httpx.Client(timeout=10, follow_redirects=True) as client:
48
+ response = client.get(url)
49
+ return {
50
+ "ok": True,
51
+ "status_code": response.status_code,
52
+ "final_url": str(response.url),
53
+ "headers": dict(response.headers),
54
+ "text": response.text[:200_000],
55
+ }
56
+ except Exception as exc:
57
+ return {"ok": False, "error": str(exc)}
58
+
59
+
60
+ def check_security_headers(headers: dict) -> list[dict]:
61
+ normalized = {k.lower(): v for k, v in headers.items()}
62
+ results = []
63
+
64
+ for header in SECURITY_HEADERS:
65
+ present = header in normalized
66
+ results.append(
67
+ {
68
+ "control": header,
69
+ "status": "present" if present else "missing",
70
+ "value": normalized.get(header, ""),
71
+ }
72
+ )
73
+
74
+ return results
75
+
76
+
77
+ def get_tls_info(url: str) -> dict:
78
+ parsed = urlparse(url)
79
+ hostname = parsed.hostname
80
+
81
+ if not hostname:
82
+ return {"ok": False, "error": "Invalid hostname"}
83
+
84
+ try:
85
+ context = ssl.create_default_context()
86
+ with socket.create_connection((hostname, 443), timeout=8) as sock:
87
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
88
+ cert = ssock.getpeercert()
89
+
90
+ return {
91
+ "ok": True,
92
+ "subject": cert.get("subject"),
93
+ "issuer": cert.get("issuer"),
94
+ "not_before": cert.get("notBefore"),
95
+ "not_after": cert.get("notAfter"),
96
+ }
97
+ except Exception as exc:
98
+ return {"ok": False, "error": str(exc)}
99
+
100
+
101
+ def extract_links(base_url: str, html: str) -> list[str]:
102
+ soup = BeautifulSoup(html, "html.parser")
103
+ links = set()
104
+
105
+ for tag in soup.find_all("a", href=True):
106
+ href = tag["href"]
107
+ absolute = urljoin(base_url, href)
108
+ parsed = urlparse(absolute)
109
+
110
+ if parsed.scheme in {"http", "https"}:
111
+ links.add(absolute.split("#")[0])
112
+
113
+ return sorted(links)
114
+
115
 
116
+ def build_markdown_report(scope: Scope, recon: dict, findings: list[Finding]) -> str:
117
+ lines = [
118
+ "# Authorized Security Assessment Report",
119
+ "",
120
+ f"Generated: {utc_now()}",
121
+ "",
122
+ "## Scope",
123
+ "",
124
+ f"- Target: `{scope.target_url}`",
125
+ f"- Authorization confirmed: `{scope.authorization_confirmed}`",
126
+ f"- Notes: {scope.engagement_notes or 'None provided'}",
127
+ "",
128
+ "## Passive Recon Summary",
129
+ "",
130
+ f"- HTTP status: `{recon.get('status_code', 'n/a')}`",
131
+ f"- Final URL: `{recon.get('final_url', 'n/a')}`",
132
+ "",
133
+ "## Security Header Review",
134
+ "",
135
+ ]
136
+
137
+ for item in recon.get("security_headers", []):
138
+ lines.append(f"- `{item['control']}`: **{item['status']}**")
139
 
140
+ lines.extend(["", "## TLS Review", ""])
141
+
142
+ tls = recon.get("tls", {})
143
+ if tls.get("ok"):
144
+ lines.append(f"- Certificate valid from: `{tls.get('not_before')}`")
145
+ lines.append(f"- Certificate valid until: `{tls.get('not_after')}`")
146
+ else:
147
+ lines.append(f"- TLS check error: `{tls.get('error', 'unknown')}`")
148
 
149
+ lines.extend(["", "## Findings", ""])
 
 
150
 
151
+ if not findings:
152
+ lines.append("No findings recorded.")
153
+ else:
154
+ for idx, finding in enumerate(findings, start=1):
155
+ lines.extend(
156
+ [
157
+ f"### {idx}. {finding.title}",
158
+ "",
159
+ f"Severity: **{finding.severity}**",
160
+ "",
161
+ "**Evidence**",
162
+ "",
163
+ finding.evidence,
164
+ "",
165
+ "**Recommendation**",
166
+ "",
167
+ finding.recommendation,
168
+ "",
169
+ ]
170
+ )
171
 
172
+ return "\n".join(lines)
173
 
 
 
 
174
 
175
  st.set_page_config(
176
+ page_title="Authorized Security Assessment Workflow",
177
+ page_icon="🛡️",
178
+ layout="wide",
179
  )
180
 
181
+ st.title("Authorized Security Assessment Workflow")
182
+ st.caption("Scope-gated passive recon, evidence logging, and report generation.")
183
 
184
+ if "findings" not in st.session_state:
185
+ st.session_state.findings = []
 
 
 
186
 
187
  with st.sidebar:
188
+ st.header("Scope Gate")
189
 
190
+ target_url = st.text_input("Target URL", placeholder="https://example.com")
191
+ authorization_confirmed = st.checkbox(
192
+ "I confirm I am authorized to assess this target"
193
  )
194
+ engagement_notes = st.text_area(
195
+ "Engagement notes",
196
+ placeholder="Bug bounty program, internal asset, written permission, etc.",
 
197
  )
198
 
199
+ run_recon = st.button("Run Passive Assessment")
200
 
201
+ if not target_url:
202
+ st.warning("Enter an authorized target URL to begin. Humanity survives another second.")
203
+ st.stop()
204
 
205
+ try:
206
+ scope = Scope(
207
+ target_url=target_url,
208
+ authorization_confirmed=authorization_confirmed,
209
+ engagement_notes=engagement_notes,
210
+ )
211
+ except Exception as exc:
212
+ st.error(f"Invalid scope input: {exc}")
213
+ st.stop()
214
 
215
+ if not scope.authorization_confirmed:
216
+ st.error("Authorization is required before running any assessment.")
217
+ st.stop()
218
 
219
+ tab_recon, tab_findings, tab_report = st.tabs(
220
+ ["Passive Recon", "Findings", "Report"]
 
 
 
221
  )
222
 
223
+ recon_data = st.session_state.get("recon_data")
224
+
225
+ if run_recon:
226
+ response = fetch_url(str(scope.target_url))
227
+
228
+ if not response["ok"]:
229
+ st.error(response["error"])
230
+ st.stop()
231
+
232
+ headers_review = check_security_headers(response["headers"])
233
+ tls_info = get_tls_info(str(scope.target_url))
234
+ links = extract_links(response["final_url"], response["text"])
235
+
236
+ recon_data = {
237
+ **response,
238
+ "security_headers": headers_review,
239
+ "tls": tls_info,
240
+ "links": links[:100],
241
+ "checked_at": utc_now(),
242
+ }
243
+
244
+ st.session_state.recon_data = recon_data
245
 
246
+ with tab_recon:
247
+ if not recon_data:
248
+ st.info("Run the passive assessment from the sidebar.")
249
  else:
250
+ col1, col2 = st.columns(2)
 
 
 
 
 
 
251
 
252
+ with col1:
253
+ st.subheader("HTTP")
254
+ st.write("Status:", recon_data["status_code"])
255
+ st.write("Final URL:", recon_data["final_url"])
256
+ st.write("Checked:", recon_data["checked_at"])
257
 
258
+ with col2:
259
+ st.subheader("TLS")
260
+ st.json(recon_data["tls"])
261
 
262
+ st.subheader("Security Headers")
263
+ st.dataframe(recon_data["security_headers"], use_container_width=True)
264
 
265
+ st.subheader("Discovered Links")
266
+ st.dataframe(recon_data["links"], use_container_width=True)
 
 
 
 
267
 
268
+ with tab_findings:
269
+ st.subheader("Add Finding")
270
+
271
+ title = st.text_input("Finding title")
272
+ severity = st.selectbox(
273
+ "Severity",
274
+ ["Informational", "Low", "Medium", "High", "Critical"],
275
+ )
276
+ evidence = st.text_area("Evidence")
277
+ recommendation = st.text_area("Recommendation")
278
+
279
+ if st.button("Save Finding"):
280
+ if not title or not evidence or not recommendation:
281
+ st.error("Title, evidence, and recommendation are required.")
282
+ else:
283
+ st.session_state.findings.append(
284
+ Finding(
285
+ title=title,
286
+ severity=severity,
287
+ evidence=evidence,
288
+ recommendation=recommendation,
289
+ )
290
+ )
291
+ st.success("Finding saved.")
292
+
293
+ st.subheader("Current Findings")
294
+
295
+ if not st.session_state.findings:
296
+ st.info("No findings recorded.")
297
+ else:
298
+ for idx, finding in enumerate(st.session_state.findings, start=1):
299
+ with st.expander(f"{idx}. {finding.title} [{finding.severity}]"):
300
+ st.write(finding.evidence)
301
+ st.write("Recommendation:")
302
+ st.write(finding.recommendation)
303
+
304
+ with tab_report:
305
+ if not recon_data:
306
+ st.info("Run recon before generating a report.")
307
+ else:
308
+ report = build_markdown_report(
309
+ scope=scope,
310
+ recon=recon_data,
311
+ findings=st.session_state.findings,
312
  )
313
 
314
+ st.subheader("Markdown Report")
315
+ st.text_area("Report", report, height=500)
316
+
317
+ st.download_button(
318
+ "Download Report",
319
+ data=report,
320
+ file_name="authorized_security_assessment_report.md",
321
+ mime="text/markdown",
322
+ )