Skip to content

Comments

Add Bluetooth device scanning and cloning for security testing#9

Merged
dshvvvshr merged 6 commits intocopilot/check-failed-tests-automationfrom
copilot/add-bluetooth-scanning-clone
Jan 28, 2026
Merged

Add Bluetooth device scanning and cloning for security testing#9
dshvvvshr merged 6 commits intocopilot/check-failed-tests-automationfrom
copilot/add-bluetooth-scanning-clone

Conversation

Copy link
Contributor

Copilot AI commented Jan 18, 2026

Implementation Complete: Bluetooth Scanning and Device Cloning ✅

All review comments have been addressed!

  • 1. Install Bluetooth scanning dependencies
  • 2. Create Bluetooth scanner module
  • 3. Create types/interfaces
  • 4. Add Bluetooth module to registry
  • 5. Create tests
  • 6. Update documentation
  • 7. Run tests and build
  • 8. Address code review feedback
    • Fixed scan() async timeout error handling - Added proper try/catch with reject
    • Made stopScan() safe against concurrent calls - Implemented in-flight promise tracking
    • Fixed importClone() Date normalization - Properly converts Date strings to Date objects
    • Track Bluetooth state internally - No longer depends on noble._state, tracks state from events
    • Added real tests for import/export - Tests validate JSON parsing, Date conversion, field validation
    • Fixed README documentation - Removed references to services/characteristics that aren't implemented
    • Fixed duplicate stop timers - scan() now manages its own timeout, doesn't pass duration to startScan()
    • Removed unused cloneId - Cleaned up logging
    • Fixed reset() to remove listeners - Prevents memory leaks
    • Fixed README /docs reference - Changed to point to repository
  • 9. Run security scan (0 vulnerabilities)

Changes Made

Core Fixes

  • Concurrency safety: stopScan() now uses promise tracking to prevent race conditions
  • State management: Internal bluetoothState field tracks state from events, no longer uses noble._state
  • Event cleanup: Added removeNobleListeners() and proper cleanup in reset()
  • Error handling: scan() timeout properly handles errors with try/catch and reject
  • Date normalization: importClone() converts JSON date strings to Date objects
  • Input validation: Improved type safety with unknown and proper type guards

Documentation

  • Updated README to accurately reflect what's implemented (advertisement data only, not service/characteristic discovery)
  • Fixed reference to non-existent /docs directory
  • Clarified security limitations

Testing

  • Added comprehensive tests for data validation and import/export logic
  • Tests validate Date normalization, JSON parsing errors, field validation
  • All 27 tests passing ✅

Final Status

✅ Build successful
✅ All tests passing (27/27)
✅ Linting clean (core code)
✅ All review comments addressed
✅ Security scan passed

Original prompt

Make this app scan for Bluetooth and identify and make clones


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 4 commits January 18, 2026 12:49
Co-authored-by: dshvvvshr <216969053+dshvvvshr@users.noreply.github.com>
Co-authored-by: dshvvvshr <216969053+dshvvvshr@users.noreply.github.com>
Co-authored-by: dshvvvshr <216969053+dshvvvshr@users.noreply.github.com>
Co-authored-by: dshvvvshr <216969053+dshvvvshr@users.noreply.github.com>
Copilot AI changed the title [WIP] Add Bluetooth scanning and cloning functionality Add Bluetooth device scanning and cloning for security testing Jan 18, 2026
Copilot AI requested a review from dshvvvshr January 18, 2026 12:57
Copy link
Owner

@dshvvvshr dshvvvshr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rerun tests. If passing by your standard go ahead with changes @copilot

@dshvvvshr dshvvvshr marked this pull request as ready for review January 25, 2026 02:20
Copilot AI review requested due to automatic review settings January 25, 2026 02:20
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ed8637d63

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 144 to 148
if (noble._state === 'poweredOn') {
resolve(true);
} else {
noble.once('stateChange', (state) => {
resolve(state === 'poweredOn');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve isReady when state is already non-poweredOn

If noble._state is already a terminal non-powered state (e.g., unsupported, unauthorized) and the initial stateChange event has already fired before isReady() is called, the promise never resolves because it only listens for a future stateChange. In that situation, startScan()/scan() await forever instead of cleanly returning false or throwing. Consider resolving immediately for any known non-poweredOn state (or adding a timeout) so callers don’t hang on systems without usable Bluetooth.

Useful? React with 👍 / 👎.

Comment on lines +167 to +171
this.discoveredDevices.clear();
this.scanning = true;

auditLogger.log(AuditLevel.INFO, 'bluetooth', 'scan-start', {
options,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset scanning flag if startScanningAsync fails

this.scanning is set to true before the call to startScanningAsync(). If startScanningAsync() rejects (permissions, adapter busy, or driver errors), the flag remains true, and subsequent calls will throw “Scan already in progress” even though no scan is running. Wrapping the start in try/catch and resetting this.scanning on failure (or setting it after a successful start) avoids the false “busy” state.

Useful? React with 👍 / 👎.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Bluetooth security module to Prime Security, enabling BLE device discovery and basic “profile cloning” for offline analysis.

Changes:

  • Introduces BluetoothScanner for BLE scanning, device identification, clone creation, and JSON import/export.
  • Registers a new bluetooth-security module in the system registry and exposes it from the package entrypoint.
  • Adds an example script, basic Jest tests, README updates, and noble dependencies.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/security/bluetooth.ts Implements BLE scanning, device caching, clone creation, and JSON import/export APIs.
src/index.ts Registers bluetooth-security module and re-exports the bluetooth module API.
src/examples/bluetooth-scanner.ts Demonstrates scanning, cloning, and exporting a device profile.
tests/security/bluetooth.test.ts Adds initial tests for data structures (non-hardware).
package.json Adds @abandonware/noble runtime dependency and @types/noble dev dependency.
README.md Updates documentation to describe Bluetooth scanning/cloning and project structure.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 217 to 226
return new Promise((resolve) => {
setTimeout(async () => {
await this.stopScan();
const scanDuration = Date.now() - startTime;

resolve({
devices: Array.from(this.discoveredDevices.values()),
scanDuration,
timestamp: new Date(),
});
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The async setTimeout callback doesn’t handle errors. If stopScan() throws, the callback rejects and resolve(...) is never called, leaving the scan() promise pending (and causing an unhandled rejection). Wrap the body in try/catch and resolve/reject deterministically.

Suggested change
return new Promise((resolve) => {
setTimeout(async () => {
await this.stopScan();
const scanDuration = Date.now() - startTime;
resolve({
devices: Array.from(this.discoveredDevices.values()),
scanDuration,
timestamp: new Date(),
});
return new Promise((resolve, reject) => {
setTimeout(async () => {
try {
await this.stopScan();
const scanDuration = Date.now() - startTime;
resolve({
devices: Array.from(this.discoveredDevices.values()),
scanDuration,
timestamp: new Date(),
});
} catch (error) {
reject(error);
}

Copilot uses AI. Check for mistakes.
Comment on lines 196 to 205
if (!this.scanning) {
return;
}

await noble.stopScanningAsync();
this.scanning = false;

auditLogger.log(AuditLevel.INFO, 'bluetooth', 'scan-stop', {
devicesFound: this.discoveredDevices.size,
});
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stopScan() is not safe against concurrent calls: this.scanning remains true until after await noble.stopScanningAsync(), so two callers can both pass the guard and call stopScanningAsync() twice. Add a stopping flag or store an in-flight promise so only one stop operation runs at a time.

Suggested change
if (!this.scanning) {
return;
}
await noble.stopScanningAsync();
this.scanning = false;
auditLogger.log(AuditLevel.INFO, 'bluetooth', 'scan-stop', {
devicesFound: this.discoveredDevices.size,
});
const self = this as any;
// If a stop operation is already in progress, wait for it instead of starting another.
if (self._stopScanPromise) {
await self._stopScanPromise;
return;
}
if (!this.scanning) {
return;
}
self._stopScanPromise = (async () => {
try {
await noble.stopScanningAsync();
this.scanning = false;
auditLogger.log(AuditLevel.INFO, 'bluetooth', 'scan-stop', {
devicesFound: this.discoveredDevices.size,
});
} finally {
self._stopScanPromise = null;
}
})();
await self._stopScanPromise;

Copilot uses AI. Check for mistakes.
Comment on lines 340 to 344
importClone(cloneJson: string): DeviceClone {
let clone: DeviceClone;

try {
clone = JSON.parse(cloneJson) as DeviceClone;
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

importClone() parses JSON and casts to DeviceClone, but metadata.clonedAt will be a string after JSON.parse, not a Date. Either store timestamps as ISO strings in the public clone format, or normalize (clone.metadata.clonedAt = new Date(...)) before returning/storing. Right now the runtime value won’t match the declared type.

Copilot uses AI. Check for mistakes.
Comment on lines 396 to 399
return {
scanning: this.scanning,
bluetoothState: noble._state, // Public API despite underscore prefix
discoveredCount: this.discoveredDevices.size,
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getStatus() (and isReady()) rely on noble._state. The underscore-prefixed property is an internal implementation detail in most libraries and can change without notice. Prefer a documented/public property (if available) or track the last state from the stateChange event in the scanner itself.

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +13
it('should allow creating and manipulating device clone data', () => {
// Test clone data structure without requiring hardware
const cloneData = {
deviceId: 'test-device-123',
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests only assert behavior of ad-hoc plain objects and never import/exercise BluetoothScanner (e.g., exportClone()/importClone() and import validation), so they won’t catch regressions in the actual module implementation. Add non-hardware tests that instantiate the scanner and cover JSON export/import (including invalid JSON / missing fields).

Copilot uses AI. Check for mistakes.
README.md Outdated
Comment on lines 265 to 269
2. **Identify** devices by their address, name, services, and characteristics
3. **Clone** device profiles including:
- Device address and name
- Advertisement data (manufacturer data, service UUIDs, tx power)
- Service and characteristic information
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README says device identification/cloning includes "services and characteristics" / "Service and characteristic information", but the current implementation only captures advertisement fields and sets profile.services to an empty array. Please align the documentation with what the code actually provides, or implement service/characteristic discovery and populate the clone profile accordingly.

Copilot uses AI. Check for mistakes.
Comment on lines 214 to 215

await this.startScan({ ...options, duration });
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scan() calls startScan({ ..., duration }), and startScan() already schedules an automatic stopScan() when options.duration is set. scan() then schedules its own timeout as well, which can lead to multiple stopScan() calls racing and potentially invoking noble.stopScanningAsync() twice. Consider having only one place responsible for the stop timer (either scan() or startScan()), or make stopping explicitly idempotent with a shared in-flight stop promise.

Suggested change
await this.startScan({ ...options, duration });
const { duration: _duration, ...scanOptions } = options;
await this.startScan(scanOptions);

Copilot uses AI. Check for mistakes.
auditLogger.log(AuditLevel.INFO, 'bluetooth', 'device-cloned', {
deviceId: device.id,
name: device.name,
cloneId: hash(device.id + Date.now().toString()).substring(0, 16),
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A cloneId is generated for logging, but clones are stored in clonedDevices keyed by device.id and the returned clone uses deviceId: device.id. This makes the logged cloneId unusable and prevents keeping multiple clones of the same device. Either remove cloneId from the log, or include/persist it (and key the map by it) if multiple clones are intended.

Suggested change
cloneId: hash(device.id + Date.now().toString()).substring(0, 16),

Copilot uses AI. Check for mistakes.
Comment on lines 413 to 417
return _bluetoothScanner;
},
reset(): void {
_bluetoothScanner = null;
}
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bluetoothScanner.reset() drops the singleton instance but does not unregister the noble.on(...) listeners that were added in the constructor. Creating a new instance after reset will stack additional listeners (duplicate logs/handlers and potential memory leak). Consider adding a destroy()/dispose() that removes listeners (or avoid exposing reset() in production code).

Copilot uses AI. Check for mistakes.
README.md Outdated
For issues and questions:
- Open an issue on GitHub
- Check the Brave Search API documentation: https://brave.com/search/api/
- Review the documentation in the `/docs` directory
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README references "the /docs directory", but there is no docs/ directory in the repository. Either add the documentation directory or update this pointer to the actual location of docs.

Suggested change
- Review the documentation in the `/docs` directory
- Review the documentation in this repository

Copilot uses AI. Check for mistakes.
@dshvvvshr
Copy link
Owner

@copilot apply changes based on the comments in this thread

… docs, and tests

Co-authored-by: dshvvvshr <216969053+dshvvvshr@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 25, 2026

@copilot apply changes based on the comments in this thread

All review comments have been addressed in commit 3735dac:

Concurrency & Error Handling:

  • scan() timeout now properly handles errors with try/catch and reject
  • stopScan() uses in-flight promise tracking to prevent race conditions
  • Removed duplicate stop timers (scan() manages timeout, not startScan())

State Management:

  • Internal bluetoothState field tracks state from events
  • No longer relies on noble._state
  • Added removeNobleListeners() for proper cleanup

Data Validation:

  • importClone() now normalizes Date fields (converts strings to Date objects)
  • Improved type safety using unknown and type guards instead of any
  • Removed unused cloneId from logging

Testing:

  • Added comprehensive tests for import/export, Date normalization, validation
  • All 27 tests passing

Documentation:

  • README updated to reflect actual implementation (advertisement data only)
  • Fixed reference to non-existent /docs directory
  • Clarified security limitations

Rerun tests. If passing by your standard go ahead with changes @copilot

Tests are passing! ✅

All 27 tests pass successfully:

  • 23 existing tests (crypto, registry)
  • 4 new Bluetooth tests (data validation, import/export)

Build is clean with no TypeScript errors. All review feedback has been addressed in commit 3735dac.

Copy link
Owner

@dshvvvshr dshvvvshr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good

@dshvvvshr dshvvvshr merged commit 786b551 into copilot/check-failed-tests-automation Jan 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants