Author: Martyn Simpson

  • Securing Vibe-Coded Apps: Control the Boundary, Not Every Line of Code

    AI-assisted development is making it possible for people across a business to create useful internal applications without traditional software-development teams. For security leaders, the challenge is deciding which risks need to be controlled centrally — without attempting to turn every small internal tool into a fully governed enterprise application.

    “Vibe coding” creates an interesting problem for security teams.

    An employee can now describe a relatively simple business problem to an AI assistant and have a functioning application remarkably quickly. That changes the economics of software development inside an organisation.

    Historically, the effort required to build an application acted as a natural gate. Development resources were scarce, infrastructure needed provisioning, deployment required technical knowledge and security often entered the process through established engineering practices.

    AI is removing much of that friction.

    The obvious security response is to introduce new friction: security reviews, architecture approvals, vulnerability assessments, coding standards and conventional SDLC controls for everything that gets built.

    I am not convinced that scales and believe there is another approach.

    Rather than trying to ensure that every small AI-built application is engineered to the same standard as a customer-facing production system, control the environment in which those applications are allowed to exist.

    The objective becomes less about trusting the application and more about establishing controls around it that the organisation can trust.

    Start with the risk, not the application

    For a small internal application, the security questions I care about most are not necessarily whether every function has been elegantly written or whether the application’s internal identity model is perfect.

    I care about questions such as:

    • Can somebody outside the organisation reach it?
    • Can an unauthorised employee reach it?
    • Can a former employee reach it?
    • Can somebody change the production code without another person being involved?
    • Can the application expose credentials?
    • Can somebody deploy arbitrary code outside our controlled environment?
    • Can we understand what happened if something goes wrong?

    Those risks can often be controlled without depending heavily on the application itself and that is an important distinction.

    If I can enforce network access, corporate identity, deployment control, source control and secrets management outside the application, then I do not need every person building an internal tool to correctly reinvent those controls themselves.

    Build a security boundary you can trust

    The model I favour is to provide a controlled environment into which these applications can be deployed.

    The exact technology is not particularly important. What matters is that certain security properties are inherited from the platform rather than being optional decisions made by each application creator.

    For example:

    • Code must live in centrally controlled source repositories.
    • Protected branches and pull-request approval prevent an individual from directly changing production code.
    • Applications deploy through a managed CI/CD path rather than informal manual deployment.
    • Production workloads run on centrally controlled infrastructure.
    • Network controls make these applications internal by default.
    • Access is placed behind a mandatory corporate OIDC identity boundary.
    • Secrets are stored through a managed secrets mechanism rather than embedded in source code.
    • Appropriate platform and access logging exists centrally.

    None of these controls require the application itself to be particularly sophisticated and that is the point.

    The less security depends upon the quality of the vibe-coded application, the less assurance we need to obtain from that application.

    Illustrative reference architecture for low-risk internal applications

    Authentication is a useful example

    Consider Joiner/Mover/Leaver controls.

    A vibe-coded application might maintain its own user database. It might even still contain an account belonging to somebody who left the organisation six months ago.

    That looks bad if you assess the application in isolation.

    But security controls should be assessed in the context of the architecture around them.

    If that application is only reachable through corporate network controls and every request must first pass an centrally managed corporate OIDC boundary, the stale local account does not necessarily represent an enterprise access-control failure.

    The former employee cannot reach the application.

    Their VPN or corporate network access has been removed and their corporate identity can no longer pass the mandatory OIDC boundary.

    The local account might be untidy. It might eventually present a data-retention issue. But we are not relying upon the application’s local account lifecycle to prevent former employees accessing the system.

    That distinction matters.

    Security teams have finite capacity. We should concentrate assurance effort on the controls on which our security model actually depends.

    The application does not have to be architecturally perfect

    Once someone has passed the mandatory corporate access boundary, I am considerably less interested in dictating how every low-risk internal application works.

    • The application owner might consume the OIDC identity and use the claims provided by it.
    • They might build their own application roles and authorisation model.
    • They might even maintain a completely separate authentication mechanism inside the application.

    That last option may be redundant or architecturally inelegant, but that does not automatically make it a security problem that central security needs to solve.

    The mandatory control remains outside it.

    This is an important principle:

    Security does not need to enforce architectural perfection where architectural imperfection does not materially increase risk.

    That mindset becomes increasingly important as AI makes software creation cheaper.

    Security assurance should follow blast radius

    The other half of this model is knowing when the lightweight approach is no longer appropriate.

    An application that helps one department manipulate some low-sensitivity internal information presents a very different risk from one that processes regulated data or performs privileged actions against production systems.

    The method used to create the application is almost irrelevant and what matters is its potential impact.

    As characteristics change, e.g. external exposure, sensitive information, privileged access, significant business dependency, consequential automated decision-making, the assurance expectations should change with them.

    At some point the application stops being something we are comfortable containing with platform controls and becomes something whose internal behaviour we genuinely need to trust.

    That is when traditional secure-development practices, deeper architecture review, vulnerability management, testing and formal ownership become proportionate.

    The mistake is assuming everything must begin there.

    Security should enable experimentation safely

    AI-assisted development is going to make internal software creation easier whether security teams like it or not.

    Trying to solve that entirely through prohibition or heavyweight approval processes risks creating an environment where the approved route is so difficult that employees look for ways around it.

    A better security outcome may come from accepting that these applications will exist and making the safe route extraordinarily easy.

    • Give people an approved place to put code.
    • Give them an approved way to deploy it.
    • Put strong identity, network, change and infrastructure controls around it.

    Then apply deeper assurance only when the risk justifies doing so.

    This is not lowering security standards.

    It is deciding where those standards need to be enforced.

    For many small internal applications, I would rather have a poorly engineered application operating inside a well-controlled security boundary than a beautifully engineered application somebody deployed into an environment I know nothing about.

    The aim is not to trust every vibe-coded app. The aim is to make sure we do not have to.

  • The Difference Between Being Secure and Being Trusted

    Security is an operating condition. Trust is a conclusion reached by somebody else.

    The distinction matters for every growing SaaS business. A company can have capable people, sensible controls and a well-managed security programme, yet still struggle to give customers, investors or its own board confidence. It can also create the opposite problem: an impressive collection of policies and badges that does not accurately reflect how risk is managed in practice.

    Being secure and being trusted are related, but they are not the same thing. Good leadership requires both.

    Security begins with operating reality

    Security is found in the decisions an organisation makes every day. It is visible in how access is granted and removed, how vulnerabilities are prioritised, how incidents are handled, how suppliers are challenged, how resilient services are designed and how leaders respond when evidence is uncomfortable.

    Policies and certifications can support this work, but they do not replace it. A control exists because people perform it consistently, its outcome can be evidenced and somebody is accountable when it fails. The real test is not whether the organisation can describe an ideal process. It is whether the process survives pressure, change and commercial urgency.

    This is why security maturity cannot be judged by the size of a policy library or the number of tools in a technology stack. It has to be judged against the risks that matter to the business and the reliability of the controls intended to manage them.

    Trust is created through understandable evidence

    Customers do not experience most of an organisation’s security controls directly. They experience the evidence provided about them. That evidence might take the form of a security questionnaire, an audit report, a Trust Center, a conversation with a CISO, a contractual commitment or the quality of the response to a difficult question.

    The same is true for boards and investors. They rarely need a tour of every technical control. They need a clear account of the material risks, the decisions being made, the evidence supporting those decisions and the uncertainty that remains.

    Trust grows when assurance is accurate, accessible and proportionate. It weakens when answers are defensive, inconsistent or buried in language that only a specialist can interpret.

    Assurance is not simply paperwork

    Customer assurance is sometimes treated as administrative overhead that appears late in a sales process. That is usually a sign that it has been separated from the security programme it is supposed to represent.

    A strong assurance approach connects operating evidence to the questions customers are trying to answer:

    • Will this service protect information appropriately?
    • Will the provider remain available when something goes wrong?
    • Can the provider explain where data is held and who can access it?
    • Will incidents be handled transparently and competently?
    • Are responsibilities understood across the service lifecycle?
    • Can the claims being made be supported by evidence?

    When assurance is designed around these decisions, it becomes part of the product and commercial experience. It reduces repeated effort, helps sales teams respond consistently and gives customers a clearer basis for accepting risk.

    Transparency needs judgement

    Transparency does not mean publishing every control detail or pretending that no weaknesses exist. It means providing the right information to the right audience, explaining limitations honestly and showing how material risks are governed.

    Too little transparency creates suspicion. Too much undirected detail can create confusion or disclose information that does not help anybody make a better decision. Good assurance therefore depends on judgement. The aim is not maximum disclosure. It is useful clarity.

    Certifications are foundations, not conclusions

    Frameworks and certifications such as ISO 27001, Cyber Essentials Plus and SOC 2 Type II can provide valuable independent evidence. They create discipline, establish common expectations and make parts of the control environment easier to assess.

    They should not be treated as a complete answer. A certification has a defined scope, a point-in-time context and limits to what it tells a customer. Mature organisations understand those limits and use certification as one part of a wider assurance story.

    Trust is tested when circumstances change

    The strongest trust signals often appear during difficult moments: a significant vulnerability, an outage, a customer escalation, a new regulatory expectation or a transaction that brings intense scrutiny.

    At those points, stakeholders look for calm communication, clear ownership and evidence that decisions are being made deliberately. A business that has invested in credible assurance before the event is better placed to explain what happened, what matters and what will happen next.

    The practical goal

    A SaaS company should not choose between being secure and being trusted. It should build an operating model in which trustworthy claims emerge from real controls and useful evidence.

    That means doing the work, understanding its limitations and making it legible to the people who depend on it.

    Security protects the organisation and its customers. Trust allows those customers, boards and investors to make informed decisions about the organisation. The businesses that understand the difference are usually better prepared for enterprise growth, external scrutiny and the moments when confidence matters most.

  • Excel Dashboard Building – Tricks

    Having spent enough of my career building Dashboards for Security Programs in various tooling, Excel continues to be the most common. However as a cell-based platform it is far from ideal when dealing with objects in general. I have however discovered the following tricks.

    Select Objects

    Sometimes you want to just select all graphs, text boxes etc to set fonts, styles or just move them. Using ctrl + left click or cmd + left click is a PITA if you have more than a few objects. If you try and click and drag (like trying to drag a bounding box around the objects) you end up selecting cells.

    Turns out there is a “mode” where you can switch to “Object Selection” rather than “Cell Selection”.

    Under the Home ribbon → Find & Select → Select Objects

    Stop Objects being affected by Cell Resizing

    Another annoying thing about Excel is when you (or your Pivot Tables) adjust Cell size (width or height) your Objects either move, resize or just get messed up.

    Use the Shape Options → Properties and make sure “Don’t move or size with cells” is selected.

    Now it doesn’t matter what you do with the underlying cells – your objects stay where you put them!

  • My Home Weather Station Build

    Note: This project is currently on hold. I’m restoring these notes from an older site draft and may come back to complete the build later.

    I wanted to design and build my own weather station using a microcontroller.

    Requirements

    1. Run on Arduino
    2. Use COTS hardware components
    3. Require minimal soldering
    4. Be battery operated
    5. Support solar charging
    6. Integrate with OpenHab
    7. Avoid destructive changes to major components

    Hardware

    ItemDetails
    MCUArduino MKR WiFi 1010
    Environmental SensorsMKR ENV Shield
    BatteryLiPo 3.7V 3300mAh
    5V RegulatorAdafruit MiniBoost 5V @ 1A – TPS61023
    Solar ChargerAdafruit Universal USB / DC / Solar Lithium Ion/Polymer Charger (bq24074)
    Solar Panel2W 6V small polysilicon solar panel

    Power

    I knew I wanted the device to live outside and not be permanently plugged in, which meant battery power and a solar panel from the outset.

    Sensors

    Sensors for the MKR WiFi 1010 were easy enough to solve. I opted for an MKR ENV Shield that could simply sit on top.

    The MKR ENV Shield includes three sensors: LPS22HB, HTS221, and TEMT6000.

    LPS22HB

    This is a compact absolute pressure sensor with a range between 260 hPa and 1260 hPa. Atmospheric pressure can be read using the readPressure() function.

    One thing to watch is that the reading needs to be adjusted based on altitude. The sensor is calibrated to provide a reading at sea level, so if your location is above that you will need to apply an offset.

    Find your altitude using GPS or an online source, then use a correction table such as:

    https://novalynx.com/manuals/bp-elevation-correction-tables.pdf

    In my case, I added the offset directly to the reading using a float.

    float pressureAdjustment = 1.64; // kPa
    pressure = ENV.readPressure() + pressureAdjustment;

    As a sanity check, compare your readings with a trusted local weather service. In the UK, the Met Office is the obvious place to start. Local airport weather data can also be useful. In my case, the nearest weather station was roughly 250ft lower than my location, so I had to take that into account when comparing values.

    HTS221

    This sensor provides temperature and humidity readings. The main functions are:

    • readTemperature()
    • readHumidity()

    TEMT6000

    The TEMT6000 is a phototransistor that changes behaviour depending on the amount of light falling on it. It is designed to align reasonably well with human eye sensitivity, so in practice it gives you a good sense of ambient light intensity.

    Its opening angle is ±60°, and while it peaks at 570 nm, it detects light in the range from 440 nm to 800 nm.

    Using the readIlluminance() command returns a value measured in lux.

    When it came to designing an enclosure, I quickly realised that with the solar panel mounted on the top, the phototransistor would be of limited practical value. I still kept the readings, as it was interesting to see whether I could detect dawn, daylight, dusk, and night from within the enclosure.

    Real-Time Clock

    The MKR WiFi 1010 includes a Real-Time Clock (RTC), which I wanted to use partly so that I could track basic uptime behaviour.

    The WiFiNINA library includes a getTime() function that polls the DHCP-advertised NTP server and returns the epoch value. Combined with the setEpoch() function from the RTCZero library, this makes it straightforward to set the internal clock correctly.

    rtc.setEpoch(WiFi.getTime());

    OpenHab

    OpenHab, for all its quirks, is perfectly capable here — but it rewards reading the documentation first rather than charging in and hoping for the best.

    MQTT

    From experience, I knew I wanted each polling cycle to send as little data as possible. I also wanted a stateless, fire-and-forget protocol. MQTT was the obvious fit, and OpenHab already supports it well.

    MQTT works by having clients connect to a broker and publish messages to topics that other clients can subscribe to. In this case, the weather station publishes data and OpenHab subscribes to it.

    As I was already running OpenHab on Ubuntu, I chose Mosquitto as the broker:

    http://www.mosquitto.org

    Recent Mosquitto builds enforce authentication by default. I did not particularly want that complexity on my internal home network, but it is still the sensible default. If you are going down this route, generate credentials for:

    • the IoT device
    • OpenHab
    • a spare admin/user account for yourself

    Documentation for Mosquitto authentication is here:

    https://mosquitto.org/documentation/authentication-methods

    You may also want an MQTT client to inspect exactly what your device is publishing. I used MQTT Explorer:

    https://mqtt-explorer.com

    You will want to think carefully about topic structure up front if you expect to add more IoT devices later.

    Issues

    Debugging

    One of the more annoying practical issues during development was switching cleanly between debugging and normal runtime. Using Serial.print() and Serial.println() everywhere is fine for quick testing, but I wanted an easy way to turn debug output on and off.

    A simple boolean flag worked well enough.

    bool debugMode = false; // set to true for debug
    ...
    if (debugMode) {
      Serial.println("debug message here");
    }

    The same pattern can be used inside functions.

    if (debugMode) {
      Serial.print("Sensor A: ");
      Serial.println(sensorAVal);
    }

    As noted under power management, I also needed a way to toggle low-power mode so that during debugging the Arduino would not go to sleep, while still waiting for the usual interval.

    void powerManagement() {
      switch (SystemState) {
        case RUNNING:
          if (lowPowerMode) {
            WiFi.end();
            SystemState = SLEEPING;
            LowPower.sleep(interval);
          } else {
            SystemState = SLEEPING;
            delay(interval);
          }
          break;
    
        case SLEEPING:
          updateWiFiStatus();
          if (status != WL_CONNECTED) {
            connectWiFi();
          }
          updateWiFiStatus();
          SystemState = RUNNING;
          break;
      }
    }

    Power management

    Power usage when running from battery turned out to be more of an issue than I expected. I was running out of power even after a full day of sun, which suggested I still had more optimisation to do around sleep states and overall power draw.

    This is one of the areas I still intend to revisit if and when I pick the project back up.

  • Obsidian Filter Empty Tasks

    If you use Templates in Obsidian and like to create an empty task item for quick typing, you will often find empty tasks showing up in your task lists.

    The first trick is to exclude your Templates folder from Tasks. You can do this in the Tasks block as below. Replace Templates with wherever you store your templates.

    ```tasks
    path does not include Templates
    ```

    If you also leave empty tasks in your main notes, you can filter them out by including the regex below. This simply matches the start of a string immediately followed by the end of the string, with nothing in between.

    ```tasks
    description regex does not match /^$/
    ```

    If you combine them together as below, you should be covered for most scenarios.

    ```tasks
    description regex does not match /^$/
    path does not include Templates
    ```

    Before

    After

  • IIS + PHP Minor Version Upgrade W/O Web Platform Installer

    I spent far too long trying to work this out.

    On Windows Server with IIS, most people would recommend using the Web Platform Installer (WebPI). The problem is that only certain specific versions are available. If, for example, you want to upgrade from PHP 7.4.13 to 7.4.27, WebPI cannot do it. You have to handle it manually.

    TL;DR: Download the new version, compare the php.ini files, and swap the directories over.

    Instructions

    Download the version of PHP you want directly from PHP for Windows:

    https://windows.php.net/download#php-7.4

    You will usually want the Non-Thread-Safe version.

    Make note of and install any dependencies, such as the Microsoft C++ Redistributable:

    https://aka.ms/vs/16/release/VC_redist.x64.exe

    Locate your current PHP installation directory. In my case, because I had originally installed PHP using WebPI, it was:

    C:\Program Files\PHP

    Extract the downloaded version next to the existing version and rename it with -new, as below.

    You will want Notepad++ with the Compare add-on, or a similar text comparison tool.

    Compare the current php.ini with the new php.ini-production.

    Important: If you originally installed PHP using WebPI, most of the changes will be towards the end of the current php.ini, particularly around extensions. Do not miss these, as I did.

    Copy across all relevant settings from the current configuration to the new php.ini-production, and make note of any newly introduced settings.

    Copy and rename the new php.ini-production to php.ini within the new directory.

    Shut down IIS from IIS Manager — at the server level, not just the site level.

    Swap the PHP directory names around.

    Start IIS again.

    Acknowledgements

    The post below was useful when I was working through this:

    https://www.itgeekrambling.co.uk/wordpress-manual-update-of-php-7-x-on-iis
  • Reverse Engineering Hozelock Cloud Controller

    I recently bought the Hozelock Cloud Controller to automate watering in my garden. While trying to integrate it with OpenHAB, I discovered a number of useful and slightly concerning things about the REST API used by the service.

    This post reflects what I found in 2019, so it should be read as a snapshot of the product and API behaviour at the time rather than a current assessment.

    Product page: https://www.hozelock.com/product/cloud-controller/

    The code I wrote at the time can be found here: https://github.com/martynjsimpson/HozelockAPI

    Overview

    The Cloud Controller is made up of two parts: the hub, which connects to your router/LAN, and the controller, which connects to the hub and is attached to the outdoor tap.

    Discovery

    By using the Hozelock cloud support page and entering a valid Hub ID, you can retrieve the status of the hub and controller. Inspecting the browser traffic showed a GET request to the following endpoint, returning a JSON array:

    GET http://hoz3.com/restful/support/hubs/{hubId}

    The first thing I noticed was that there appeared to be no authentication on the endpoint, you only needed a valid Hub ID.

    If you make a direct call using an invalid Hub ID, the endpoint is kind enough to tell you the expected regular expression for a valid ID.

    GET http://hoz3.com/restful/support/hubs/xxxxxx
    
    {
        "errorCode": 134611463,
        "httpStatus": 400,
        "errorMessage": "GET request to '/restful/support/hubs/xxxxxx' failed due to invalid parameter 'hubID': Expected a valid Hub ID matching [0-9A-Z]{6}",
        "cause": {
            "method": "GET",
            "url": "/restful/support/hubs/xxxxxx",
            "contentType": null
        }
    }

    If anybody wants to do the maths on the total number of possible permutations, I would still be interested.

    It is also worth noting that this all worked over HTTP. HTTPS was supported, but it was not forced and HTTP did not appear to redirect. Throughout this post I may mix HTTP and HTTPS, but the point is that both appeared possible unless stated otherwise.

    From there, I started looking at what else the API supported.

    Hub

    OPTIONS https://hoz3.com/restful/support/hubs/{hubId}
    
    {
        "errorCode": 0,
        "allowedMethods": [
            "GET",
            "OPTIONS"
        ]
    }

    There is nothing especially exciting here from an action point of view, but the data returned for the hub can include potentially sensitive information such as location if the user has configured it. From memory, this appeared to include things like city, country, local time, and time zone.

    Next, I worked through the rest of the JSON returned by GET /{hubId}.

    Schedules

    GET https://hoz3.com/restful/support/hubs/{hubId}/schedules

    This returns an array of all schedules for the hub. Note that this does not necessarily mean the schedule is currently applied to the controller.

    In the sample I captured, the watering was configured for sunrise and sunset, which meant two entries per day.

    Timestamps appeared to use Unix epoch milliseconds.
    -2000 appeared to mean sunrise and -1000 appeared to mean sunset (or an offset from them). Duration was also expressed in milliseconds.

    [
        {
            "scheduleID": "{scheduleId}",
            "name": "{scheduleName}",
            "description": null,
            "scheduleDays": {
                "Monday": {
                    "dayOfWeek": "Monday",
                    "wateringEvents": [
                        {
                            "startTime": -2000,
                            "endTime": 898000,
                            "duration": 900000,
                            "enabled": true
                        },
                        {
                            "startTime": -1000,
                            "endTime": 899000,
                            "duration": 900000,
                            "enabled": true
                        }
                    ]
                }
            }
        }
    ]

    You can also retrieve a specific schedule by appending the scheduleId to the URL.

    GET https://hoz3.com/restful/support/hubs/{hubId}/schedules/{scheduleId}

    Looking at what was supported at the schedules endpoint:

    OPTIONS https://hoz3.com/restful/support/hubs/{hubId}/schedules/
    
    {
        "errorCode": 0,
        "allowedMethods": [
            "GET",
            "HEAD",
            "OPTIONS"
        ]
    }

    Not much of use there, although HEAD could still be interesting.

    However, the specific schedule endpoint was more revealing.

    OPTIONS https://hoz3.com/restful/support/hubs/{hubId}/schedules/{scheduleId}
    
    {
        "errorCode": 0,
        "allowedMethods": [
            "DELETE",
            "GET",
            "HEAD",
            "PATCH",
            "POST",
            "PUT",
            "OPTIONS"
        ]
    }

    This was far more interesting, as POST, PUT, and PATCH appeared to allow schedule creation, replacement/upsert, and updates. Even DELETE was available.

    At the time I was able to adjust a schedule using PATCH by changing the name, but I never fully worked out the complete payloads for creating, updating, or deleting schedules.

    Controllers

    One or more controllers can be associated with a single hub, for example, one for the front garden and one for the rear.

    GET https://hoz3.com/restful/support/hubs/{hubId}/controllers
    
    {
        "controllers": [
            {
                "name": "{controllerName}",
                "image": null,
                "controllerID": "0",
                "scheduleID": "{scheduleId}",
                "schedule": {
                    "scheduleID": "{scheduleId}",
                    "name": "{scheduleName}"
                },
                "hasWaterNowEvent": false,
                "pause": null,
                "adjustment": null,
                "waterNowEvent": null,
                "currentWateringEvent": null,
                "nextWateringEvent": {
                    "startTime": 1556219700000,
                    "endTime": 1556220600000,
                    "duration": 900000,
                    "enabled": true
                },
                "lastCommunicationWithServer": 1556211067000,
                "nextCommunicationWithServer": 1556212140000,
                "batteryStatus": "OK",
                "signalStrength": "GOOD",
                "overrideScheduleDuration": null,
                "isChildlockEnabled": false,
                "isWatering": false,
                "isPanelRemoved": false,
                "isTested": true,
                "isAdjusted": false,
                "isScheduleUpToDate": true,
                "isPaused": false
            }
        ],
        "inPairingMode": false,
        "lastServerContactDate": 1556211067000,
        "hubResetRequired": false,
        "controllerResetRequired": false,
        "isUresponsive": false
    }

    Again, this returned an array of all controllers, and you could address a specific controller by appending the controllerId to the URL:

    GET https://hoz3.com/restful/support/hubs/{hubId}/controllers/{controllerId}

    Both the controllers endpoint and the specific controller endpoint appeared to support only GET, HEAD, and OPTIONS.

    OPTIONS https://hoz3.com/restful/support/hubs/{hubId}/controllers/{controllerId}
    OR
    OPTIONS https://hoz3.com/restful/support/hubs/{hubId}/controllers
    
    {
        "errorCode": 0,
        "allowedMethods": [
            "GET",
            "HEAD",
            "OPTIONS"
        ]
    }

    Actions

    This section is largely credited to anthonyangel (see Credits below).

    There appeared to be an undocumented part of the API called Actions, which allowed tasks to be submitted for the hub, and therefore the controller, to pick up.

    GET http://hoz3.com/restful/support/hubs/{hubId}/controllers/actions/
    
    {
        "errorCode": 0,
        "actions": [
            "pause",
            "unpause",
            "adjust",
            "unadjust",
            "waterNow",
            "stopWatering",
            "setMode",
            "ping"
        ]
    }

    Using the request structures worked out by anthonyangel, it was possible to send a POST with a body to issue a waterNow command.

    POST http://hoz3.com/restful/support/hubs/{hubId}/controllers/actions/waterNow
    
    Request Body
    {
        "controllerIDs": [{controllerId}],
        "duration": 300000
    }
    
    Response
    {
        "errorCode": 0
    }

    Sure enough, after the controller’s polling delay, up to around 20 minutes, the phone app reported that watering had started.

    Available actions

    • pause — pauses watering for a number of days
    • unpause — removes a pause
    • adjust — appears to increase watering duration by a percentage for a period of time
    • unadjust — removes the adjustment
    • waterNow — starts a watering session
    • stopWatering — stops watering
    • setMode — seemed to expect a mode parameter, though I did not fully decode it
    • ping — despite being listed, it did not appear to be implemented when I tested it

    One useful pattern here was that if you built a payload with only controllerIDs, the error responses often told you what additional data was missing. Having the mobile app to hand also helped when trying to infer how API calls mapped to UI actions.

    Conclusions

    As is often the case with newer IoT products, security seemed to have been treated as secondary. Admittedly, the impact of somebody tampering with a garden watering system is not exactly catastrophic, perhaps they waste some water or kill a few plants, but it still reflects poor design choices.

    That said, the controller → hub → cloud model appeared to limit the risk of this leading directly to compromise of the wider LAN.

    Basic measures such as forcing HTTPS appeared to have been missed. Enabling and enforcing TLS at the service edge is hardly unusual, although I accept there may have been implementation constraints on the hub side that were not obvious from outside.

    Authentication on the endpoints, even for GET operations, should have been a minimum expectation. Relying on obscurity is not a sensible control. Others had also reported an apparent lack of rate limiting, which meant brute-forcing Hub IDs looked technically feasible.

    At the time, I also wondered whether companies like Hozelock should provide proper developer portals, API documentation, and supported integration mechanisms. That would not remove the need for real security controls, but it would at least reduce the incentive for enthusiasts to reverse engineer the platform just to make it work with their home automation stack.

    Overall, I really liked the product. I am terrible at remembering to water the garden, and this solved a genuine problem for me. Initial setup was a bit painful, I suspect because of previous pairing attempts, but a full factory reset of the hub and controller fixed it.

    More broadly, it felt like another example of an IoT product reaching market before security had been given the attention it deserved. Companies building connected devices need to treat security design with the same seriousness they would apply to their internal applications and services.

    At the time, I looked for somewhere to report these issues but could not find an obvious route.

    Credits

    The original inspiration for digging into this came from the Home Assistant community, particularly a post by anthonyangel:

    https://community.home-assistant.io/t/having-hozelock-cloud-controller-kit-intergration/55694/3

    He appears to have reported the lack of security around July 2018, and it did not look like much came of it.

  • IIS 7 Start / Stop / Restart

    If you need to start, stop, or restart IIS from the command line, the quickest option is to use iisreset.

    Commands

    Start IIS:

    iisreset /start

    Stop IIS:

    iisreset /stop

    Restart IIS:

    iisreset

    Note

    iisreset restarts the entire IIS service, so use it with care on shared or production servers. If you only need to restart a specific site or application pool, IIS Manager or PowerShell may be a better option.

  • SCP a Directory in OSX

    When copying a directory with scp on macOS, the placement of the trailing slash matters.

    If you want to copy the folder source and keep the folder itself in the destination path, use:

    scp -r /path/to/source user@host:/path/for/dest/

    That will copy the source directory into /path/for/dest/.

    For example, if you run:

    scp -r /Users/martyn/source user@example.com:/tmp/

    the result on the remote host will be:

    /tmp/source
  • Rules to follow while naming a SQL Server Instance

    When naming a SQL Server instance, it is worth keeping a few practical rules in mind.

    General rules

    • Instance names are limited to 16 characters.
    • The first character must be a letter.
    • Names cannot contain spaces or special characters such as \, /, :, *, ?, ", <, >, or |.
    • Avoid names that are difficult to distinguish from the host name or from other instances already in use.
    • Keep names short, clear, and meaningful.

    Good practice

    In addition to the technical limits, a sensible naming convention will save confusion later.

    A good instance name should:

    • make it obvious what the instance is for
    • be consistent across environments
    • be easy for administrators and support teams to recognise
    • avoid unnecessary abbreviations unless they are well understood internally

    For example, you might choose names based on:

    • application or service
    • environment, such as DEV, TEST, or PROD
    • region or business unit, if that is relevant

    Examples

    Reasonable examples might include:

    • APP01
    • FINANCE
    • REPORTING
    • CRMTEST

    Poor examples might include:

    • SQL Server
    • 123SQL
    • INSTANCE!
    • THISISFARTOOLONGFORSQL

    Final thought

    The technical rules are simple, but the operational impact of a poor naming convention can linger for years. A clear, consistent standard is usually worth agreeing early.