Initial commit: Imperva WAF demo lab

Flight-booking app scaffolded from the source running on the GCP host,
plus the four-screen frontend and three OWASP attack endpoints (SQLi,
reflected XSS, path traversal) built and locally verified against a
live uvicorn/vite dev pair. Includes deploy.sh (local build -> ship
over ssh -> swap on host) and docs/ carried over from the handoff.
This commit is contained in:
matt committed 2026-07-29 11:08:56 -07:00
commit 4d60fdd139
28 files changed
+2609

No files matched your search

@@ -0,0 +1,37 @@
<script setup>
import { ref } from 'vue'
import { downloadBoardingPass } from '../api.js'
const file = ref('BP-1001.txt')
const content = ref('')
const error = ref('')
async function runDownload() {
error.value = ''
content.value = ''
const { ok, status, text } = await downloadBoardingPass(file.value)
if (!ok) {
error.value = `Request failed (${status})`
return
}
content.value = text
}
</script>
<template>
<section class="panel">
<h2>Boarding Pass Download</h2>
<div class="field-row">
<input
type="text"
v-model="file"
placeholder="Boarding pass filename (e.g. BP-1001.txt)"
@keyup.enter="runDownload"
/>
<button class="primary" @click="runDownload">Download</button>
</div>
<p class="status error" v-if="error">{{ error }}</p>
<pre class="result" v-if="content">{{ content }}</pre>
</section>
</template>
+56
View File
@@ -0,0 +1,56 @@
<script setup>
import { ref } from 'vue'
import { searchFlights } from '../api.js'
const term = ref('')
const queryEcho = ref('')
const results = ref([])
const searched = ref(false)
async function runSearch() {
const { body } = await searchFlights(term.value)
queryEcho.value = body?.query_echo ?? ''
results.value = body?.results ?? []
searched.value = true
}
</script>
<template>
<section class="panel">
<h2>Flight Search</h2>
<div class="field-row">
<input
type="text"
v-model="term"
placeholder="Search destinations (e.g. Mars)"
@keyup.enter="runSearch"
/>
<button class="primary" @click="runSearch">Search</button>
</div>
<!-- Intentionally unescaped: the API echoes the raw search term back
verbatim and this renders it as HTML, not text. -->
<p class="echo" v-if="searched" v-html="queryEcho"></p>
<table v-if="results.length">
<thead>
<tr>
<th>Flight</th>
<th>Origin</th>
<th>Destination</th>
<th>Departs</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr v-for="f in results" :key="f.id">
<td>{{ f.id }}</td>
<td>{{ f.origin }}</td>
<td>{{ f.destination }}</td>
<td>{{ f.depart }}</td>
<td>${{ f.price.toLocaleString() }}</td>
</tr>
</tbody>
</table>
</section>
</template>
+32
View File
@@ -0,0 +1,32 @@
<script setup>
import { ref } from 'vue'
import { login } from '../api.js'
const username = ref('')
const password = ref('')
const message = ref('')
const success = ref(false)
async function runLogin() {
const { body } = await login(username.value, password.value)
success.value = body?.status === 'authenticated'
message.value = success.value
? `Authenticated. Token: ${body.token}`
: (body?.message ?? 'Login failed')
}
</script>
<template>
<section class="panel">
<h2>Login</h2>
<div class="field-row">
<input type="text" v-model="username" placeholder="Username" />
</div>
<div class="field-row">
<input type="password" v-model="password" placeholder="Password" @keyup.enter="runLogin" />
<button class="primary" @click="runLogin">Log in</button>
</div>
<p class="status" :class="success ? 'ok' : 'error'" v-if="message">{{ message }}</p>
</section>
</template>
+55
View File
@@ -0,0 +1,55 @@
<script setup>
import { ref } from 'vue'
import { searchUsers } from '../api.js'
const q = ref('')
const results = ref([])
const error = ref('')
async function runSearch() {
error.value = ''
const { ok, body } = await searchUsers(q.value)
if (!ok) {
error.value = body?.detail ?? 'Search failed'
results.value = []
return
}
results.value = body ?? []
}
</script>
<template>
<section class="panel">
<h2>Passenger / Admin Lookup</h2>
<div class="field-row">
<input
type="text"
v-model="q"
placeholder="Search by username"
@keyup.enter="runSearch"
/>
<button class="primary" @click="runSearch">Search</button>
</div>
<p class="status error" v-if="error">{{ error }}</p>
<table v-if="results.length">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Email</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr v-for="u in results" :key="u.id">
<td>{{ u.id }}</td>
<td>{{ u.username }}</td>
<td>{{ u.email }}</td>
<td>{{ u.role }}</td>
</tr>
</tbody>
</table>
</section>
</template>